Apache Axis2 Web Services: Writing an Axis2 Module

0
126
13 min read

 

Apache Axis2 Web Services, 2nd Edition

Apache Axis2 Web Services, 2nd Edition

Create secure, reliable, and easy-to-use web services using Apache Axis2.

  • Extensive and detailed coverage of the enterprise ready Apache Axis2 1.5 Web Services / SOAP / WSDL engine.
  • Attain a more flexible and extensible framework with the world class Axis2 architecture.
  • Learn all about AXIOM – the complete XML processing framework, which you also can use outside Axis2.
  • Covers advanced topics like security, messaging, REST and asynchronous web services.
  • Written by Deepal Jayasinghe, a key architect and developer of the Apache Axis2 Web Service project; and Afkham Azeez, an elected ASF and PMC member.

        Read more about this book      

(For more resources on this subject, see here.)

Web services are gaining a lot of popularity in the industry and have become one of the major enabler for application integration. In addition, due to the flexibility and advantages of using web services, everyone is trying to enable web service support for their applications. As a result, web service frameworks need to support new and more custom requirements. One of the major goals of a web service framework is to deliver incoming messages into the target service. However, just delivering the message to the service is not enough; today’s applications are required to have reliability, security, transaction, and other quality services.

In our approach, we will be using code sample to help us understand the concepts better.

Brief history of the Axis2 module

Looking back at the history of Apache Web Services, the Handler concept can be considered as one of the most useful and interesting ideas. Due to the importance and flexibility of the handler concept, Axis2 has also introduced it into its architecture. Notably, there are some major differences in the way you deploy handlers in Axis1 and Axis2. In Axis1, adding a handler requires you to perform global configuration changes and for an end user, this process may become a little complex. In contrast, Axis2 provides an easy way to deploy handlers. Moreover, in Axis2, deploying a handler is similar to deploying a service and does not require global configuration changes.

At the design stage of Axis2, one of the key considerations was to have a mechanism to extend the core functionality without doing much. One of the main reasons behind the design decision was due to the lesson learned from supporting WS reliable messaging in Axis1. The process of supporting reliable messaging in Axis1 involved a considerable amount of work, and part of the reason behind the complex process was due to the limited extensibility of Axis1 architecture. Therefore, learning from a session in Axis1, Axis2 introduced a very convenient and flexible way of extending the core functionality and providing the quality of services. This particular mechanism is known as the module concept.

Module concept

One of the main ideas behind a handler is to intercept the message flow and execute specific logic. In Axis2, the concept of a module is to provide a very convenient way of deploying service extension. We can also consider a module as a collection of handlers and required resources to run the handlers (for example, third-party libraries). One can also consider a module as an implementation of a web service standard specification. As an illustration, Apache Sandesha is an implementation of WS-RM specification. Apache Rampart is an implementation of WS-security; likewise, in a general module, is an implementation of a web service specification. One of the most important features and aspects of the Axis2 module is that it provides a very easy way to extend the core functionality and provide better customization of the framework to suit complex business requirements. A simple example would be to write a module to log all the incoming messages or to count the number of messages, if requested.

Module structure

Axis1 is one of the most popular web service frameworks and it provides very good support for most of the web service standards. However, when it comes to new and complex specifications, there is a significant amount of work we need to do to achieve our goals. The problem becomes further complicated when the work we are going to do involves handlers, configuration, and third-party libraries. To overcome this issue, the Axis2 module concept and its structure can be considered as a good candidate. As we discussed in the deployment section, both Axis2 services and modules can be deployed as archive files. Inside any archive file, we can have configuration files, resources, and the other things that the module author would like to have.

It should be noted here that we have hot deployment and hot update support for the service; in other words, you can add a service when the system is up and running. However, unfortunately, we cannot deploy new modules when the system is running. You can still deploy modules, but Axis2 will not make the changes to the runtime system (we can drop them into the directory but Axis2 will not recognize that), so we will not use hot deployment or hot update. The main reason behind this is that unlike services, modules tend to change the system configurations, so performing system changes at the runtime to an enterprise-level application cannot be considered a good thing at all.

Adding a handler into Axis1 involves global configuration changes and, obviously, system restart. In contrast, when it comes to Axis2, we can add handlers using modules without doing any global level changes. There are instances where you need to do global configuration changes, which is a very rare situation and you only need to do so if you are trying to add new phases and change the phase orders. You can change the handler chain at the runtime without downer-starting the system.

Changing the handler chain or any global configuration at the runtime cannot be considered a good habit. This is because in a production environment, changing runtime data may affect the whole system. However, at the deployment and testing time this comes in handy.

The structure of a module archive file is almost identical to that of a service archive file, except for the name of the configuration file. We know that for a service archive file to be a valid one, it is required to have a services.xml. In the same way, for a module to be a valid module archive, it has to have a module.xml file inside the META-INF directory of the archive. A typical module archive file will take the structure shown in the following screenshot. We will discuss each of the items in detail and create our own module in this article as well.

Apache Axis2 1.5 Web Services: Writing an Axis2 Module

Module configuration file (module.xml)

The module archive file is a self-contained and self-described file. In other words, it has to have all the configuration required to be a valid and useful module. Needless to say, that is the beauty of a self-contained package. The Module configuration file or module.xml file is the configuration file that Axis2 can understand to do the necessary work.

A simple module.xml file has one or more handlers. In contrast, when it comes to complex modules, we can have some other configurations (for example, WS policies, phase rules) in a module.xml. First, let’s look at the available types of configurations in a module.xml. For our analysis, we will use a module.xml of a module that counts all the incoming and outgoing messages. We will be discussing all the important items in detail and provide a brief description for the other items:

  • Handlers alone with phase rules
  • Parameters
  • Description about module
  • Module implementation class
  • WS-Policy
  • End points

Handlers and phase rules

A module is a collection of handlers, so a module could have one or more handlers. Irrespective of the number of handlers in a module, module.xml provides a convenient way to specify handlers. Most importantly, module.xml can be used to provide enough configuration options to add a handler into the system and specify the exact location where the module author would like to see the handler running. Phase rules can be used as a mechanism to tell Axis2 to put handlers into a particular location in the execution chain, so now it is time to look at them with an example.

Before learning how to write phase rules and specifying handlers in a module.xml, let’s look at how to write a handler. There are two ways to write a handler in Axis2:

  • Implement the org.apache.axis2.engine.Handler interface
  • Extend the org.apache.axis2.handlers.AbstractHandler abstract class

In this article, we are going to write a simple application to provide a better understanding of the module. Furthermore, to make the sample application easier, we are going to ignore some of the difficulties of the Handler API. In our approach, we will extend the AbstractHandler. When we extend the abstract class, we only need to implement one method called invoke. So the following sample code will illustrate how to implement the invoke method:

public class IncomingCounterHandler extends AbstractHandler implements
CounterConstants {
public InvocationResponse invoke(MessageContext messageContext) throws
AxisFault {
//get the counter property from the configuration context
ConfigurationContext configurationContext = messageContext.
getConfigurationContext();
Integer count =
(Integer) configurationContext.getProperty(INCOMING_
MESSAGE_COUNT_KEY);
//increment the counter
count = Integer.valueOf(count.intValue() + 1 + «»);
//set the new count back to the configuration context
configurationContext.setProperty(INCOMING_MESSAGE_COUNT_KEY,
count);
//print it out
System.out.println(«The incoming message count is now « +
count);
return InvocationResponse.CONTINUE;
}
}

As we can see, the method takes MessageContext as a method parameter and returns InvocationResponse as the response. You can implement the method as follows:

  1. First get the configurationContext from the messageContext.
  2. Get the property value specified by the property name.
  3. Then increase the value by one.
  4. Next set it back to configurationContext.
  5. In general, inside the invoke method, as a module author, you have to do all the logic processing, and depending on the result you get, we can decide whether you let AxisEngine continue, suspend, or abort. Depending on your decision, you can return to one of the three following allowed return types:
    • InvocationResponse.CONTINUE
      Give the signal to continue the message
    • InvocationResponse.SUSPEND
      The message cannot continue as some of the conditions are not satisfied yet, so you need to pause the execution and wait.
    • InvocationResponse.ABORT
      Something has gone wrong, therefore you need to drop the message and let the initiator know about it.
  6. The message cannot continue as some of the conditions are not satisfied yet, so you need to pause the execution and wait.
  7. InvocationResponse.ABORT
  8. Something has gone wrong, therefore you need to drop the message and let the initiator know about it.

The corresponding CounterConstants class a just a collection of constants and will look as follows:

public interface CounterConstants {
String INCOMING_MESSAGE_COUNT_KEY = "incoming-message-count";
String OUTGOING_MESSAGE_COUNT_KEY = "outgoing-message-count";
String COUNT_FILE_NAME_PREFIX = "count_record";
}

As we already mentioned, the sample module we are going to implement is for counting the number of request coming into the system and the number of messages going out from the system. So far, we have only written the incoming message counter and we need to write the outgoing message counter as well, and the implementation of the out message count hander will look like the following:

public class OutgoingCounterHandler extends AbstractHandler implements
CounterConstants {
public InvocationResponse invoke(MessageContext messageContext)
throws AxisFault {
//get the counter property from the configuration context
ConfigurationContext configurationContext = messageContext.
getConfigurationContext();
Integer count =
(Integer) configurationContext.getProperty(OUTGOING_
MESSAGE_COUNT_KEY);
//increment the counter
count = Integer.valueOf(count.intValue() + 1 + «»);
//set it back to the configuration
configurationContext.setProperty(OUTGOING_MESSAGE_COUNT_KEY,
count);
//print it out
System.out.println(«The outgoing message count is now « +
count);
return InvocationResponse.CONTINUE;
}
}

The implementation logic will be exactly the same as the incoming handler processing, except for the property name used in two places.

Module implementation class

When we work with enterprise-level applications, it is obvious that we have to initialize various settings such as database connections, thread pools, reading property, and so on. Therefore, you should have a place to put that logic in your module. We know that handlers run only when a request comes into the system but not at the system initialization time. The module implementation class provides a way to achieve system initialization logic as well as system shutdown time processing. As we mentioned earlier, module implementation class is optional. A very good example of a module that does not have a module implementation class is the Axis2 addressing module. However, to understand the concept clearly in our example application, we will implement a module implementation class, as shown below:

public class CounterModule implements Module, CounterConstants {
private static final String COUNTS_COMMENT = "Counts";
private static final String TIMESTAMP_FORMAT = "yyMMddHHmmss";
private static final String FILE_SUFFIX = ".properties";

public void init(ConfigurationContext configurationContext,
AxisModule axisModule) throws AxisFault {
//initialize our counters
System.out.println("inside the init : module");
initCounter(configurationContext, INCOMING_MESSAGE_COUNT_KEY);
initCounter(configurationContext, OUTGOING_MESSAGE_COUNT_KEY);
}

private void initCounter(ConfigurationContext
configurationContext,
String key) {
Integer count = (Integer) configurationContext.
getProperty(key);
if (count == null) {
configurationContext.setProperty(key, Integer.
valueOf("0"));
}
}

public void engageNotify(AxisDescription axisDescription) throws
AxisFault {
System.out.println("inside the engageNotify " +
axisDescription);
}

public boolean canSupportAssertion(Assertion assertion) {
//returns whether policy assertions can be supported
return false;
}

public void applyPolicy(Policy policy,
AxisDescription axisDescription) throws
AxisFault {
// Configuure using the passed in policy!
}

public void shutdown(ConfigurationContext configurationContext)
throws AxisFault {
//do cleanup - in this case we'll write the values of the
counters to a file
try {
SimpleDateFormat format = new SimpleDateFormat(TIMESTAMP_
FORMAT);
File countFile = new File(COUNT_FILE_NAME_PREFIX + format.
format(new Date()) + FILE_SUFFIX);
if (!countFile.exists()) {
countFile.createNewFile();
}

Properties props = new Properties();
props.setProperty(INCOMING_MESSAGE_COUNT_KEY,
configurationContext.getProperty(INCOMING_MESSAGE_
COUNT_KEY).toString());
props.setProperty(OUTGOING_MESSAGE_COUNT_KEY,
configurationContext.getProperty(OUTGOING_MESSAGE_
COUNT_KEY).toString());
//write to a file
props.store(new FileOutputStream(countFile), COUNTS_
COMMENT);
} catch (IOException e) {
//if we have exceptions we'll just print a message and let
it go
System.out.println("Saving counts failed! Error is " +
e.getMessage());
}
}
}

As we can see, there are a number of methods in the previous module implementation class. However, notably not all of them are in the module interface. The module interface has only the following methods, but here we have some other methods for supporting our counter module-related stuff:

  • init
  • engageNotify
  • applyPolicy
  • shutdown

At the system startup time, the init method will be called, and at that time, the module can perform various initialization tasks. In our sample module, we have initialized both in-counter and out-counter.

When we engage this particular module to the whole system, to a service, or to an operation, the engageNotify method will be called. At that time, a module can decide whether the module can allow this engagement or not; say for an example, we try to engage the security module to a service, and at that time, the module finds out that there is a conflict in the encryption algorithm. In this case, the module will not be able to engage and the module throws an exception and Axis2 will not engage the module. In this example, we will do nothing inside the engageNotify method.

As you might already know, WS-policy is one of the key standards and plays a major role in the web service configuration. When you engage a particular module to a service, the module policy should be applied to the service and should be visible when we view the WSDL of that service. So the applyPolicy method sets the module policy to corresponding services or operations when we engage the module. In this particular example, we do not have any policy associated with the module, so we do not need to worry about this method as well.

As we discussed in the init method, the method shutdown will be called when the system has to shut down. So if we want to do any kind of processing at that time, we can add this logic into that particular method. In our example, for demonstration purposes, we have added code to store the counter values in a file.

LEAVE A REPLY

Please enter your comment!
Please enter your name here