Extending Axis2

Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.
  • By Amila Suriarachchi
  • 29 Dec, 2008

Table of Contents

Introduction

Although custom deployers and Axis2 modules have been defined with clear interfaces, it is not possible to extend Axis2, without the knowledge of Axis2 kernel architecture and the context in which they should be used. Therefore, this article begins with a brief introduction to Axis2 kernel architecture. After that, it uses a sample service using the Axis2 API to further clarify it. Finally, we illustrate samples to demonstrate each feature. Hence, this article elaborates the basics behind the Axis2 kernel architecture and all above features, to enable developers use that knowledge to customize Axis2 to meet their requirements.

Axis2 Kernel Architecture

This article looks at the Axis2 kernel architecture in two different view points, namely, Axis2 information model and Axis2 SOAP processing model in order to explain concepts behind custom deployers and Axis2 modules. A complete architecture document can be found under Axis2 architecture documentation[1]. Current Axis2 information model consists of binding objects in its' objects hierarchy, and message formatters and builders at SOAP processing model. However, this article does not take any of that into consideration, as we only discuss the basics here.

Axis2 Information Model

Axis2 uses an information model[2] to store all information required for processing messages. This consists of two parts called the description and the context hierarchy. Description hierarchy contains static information, while the context hierarchy contains more dynamic information. Infact, Axis2 information model contains all information including the Axis2 module, Axis Configuration , service, operation, transports, message formatters, message builders etc. At Axis2 start up, deployment engine first creates the axisConfiguration object by reading the axis2.xml file. Then populate all other information by reading the service archive files and module archive files using respective deployers. Hence, if there is an alternative method to add new Axis service objects to the description hierarchy during deployment, then that can be used to add new Axis services in a different way. Custom deployers can be used to do this.

Axis2 SOAP Processing Model

SOAP processing model[3] is used to process incoming service requests and out going response messages. At the receiving side, first transport receiver receives the message and builds a message context object and delivers the message to the Axis engine. Axis engine invokes in flow handlers for the given message, and at the end, it invokes the message receiver. Here, it should be noted that it is up to the message receiver XML info set is represented as an axiom object model and data binding (i.e converting xml info set to java objects and converting java objects to xml info set) is taken place at the message receiver. Finally, message receiver invokes service methods with Java objects. A reverse process occurs at the sender's end. First, the user application provides Java objects to the stub. Then, the stub object does the data binding to obtain the Axiom info set model and the message is given to the client API to be sent through the Axis engine. After Axis engine invokes all out flow handlers, it invokes the transport sender in order to send the message. Transport sender creates the serialization format of the message and sends it to the service end point. As in description hierarchy example, if there is a method to add new handlers to Axis engine at deployment time, then the SOAP processing model can be extended. Axis2 modules can be used for this purpose.

Developing services with Axis2 API

As explained earlier axis2 data binding happens after message receiver and before the client api. Therefore this sample explain how to create a service only using message receivers and access it using service client API with out using any data binding code. Sample source code which has been tested with the axis2-1.4.1, for all samples can be found here.

Message receivers

At server side, message exchange pattern (MEP) is determined by the message receiver. Therefore, there are two abstract message receivers called 'AbstractInMessageReceiver' and 'AbstractInOutMessageReceiver' to develop in only and in out operations respectively. Following code shows two such message receivers used in this sample:

public class SampleInOnlyMessageReceiver extends AbstractInMessageReceiver {
    protected void invokeBusinessLogic(MessageContext inMessageContext) throws AxisFault {
        System.out.println("Got the message ==> " +
                inMessageContext.getEnvelope().getBody().getFirstElement().toString());
    }
}
public class SampleInOutMessageReceiver extends AbstractInOutMessageReceiver {
    public void invokeBusinessLogic(MessageContext inMessageContext,
                                    MessageContext outMessageContext) throws AxisFault {
        System.out.println("Got the message ==> " +
                inMessageContext.getEnvelope().getBody().getFirstElement().toString());
        // creating the response message
        // getting the soap namesapce uri of the incomming message
        String soapNamespace = inMessageContext.getEnvelope().getNamespace().getNamespaceURI();
        // creating a soap factory according the the soap namespce uri
        SOAPFactory soapFactory = null;
        if (soapNamespace.equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI)){
            soapFactory = OMAbstractFactory.getSOAP11Factory();
        } else if (soapNamespace.equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)){
            soapFactory = OMAbstractFactory.getSOAP12Factory();
        } else {
            System.out.println("Unknow soap message");
        }

        SOAPEnvelope responseEnvelope = soapFactory.getDefaultEnvelope();

        // creating a body element
        OMFactory omFactory = OMAbstractFactory.getOMFactory();
        OMNamespace omNamespace = omFactory.createOMNamespace("http://sample.api","ns1");
        OMElement omElement = omFactory.createOMElement("Response", omNamespace);
        omElement.setText("Sucessfully got the message");
        responseEnvelope.getBody().addChild(omElement);

        outMessageContext.setEnvelope(responseEnvelope);
    }
}

For in out operations, out message SOAP envelope should be created at the message receiver. This code simply prints messages received and creates a new message as a response.

Service descriptor file

<serviceGroup>
    <service name="SampleService">
        <description>Sample Service</description>
        <messageReceivers>
            <messageReceiver
                    mep="http://www.w3.org/2004/08/wsdl/in-only"
                    class="sample.api.service.SampleInOnlyMessageReceiver"/>
            <messageReceiver
                    mep="http://www.w3.org/2004/08/wsdl/in-out"
                    class="sample.api.service.SampleInOutMessageReceiver"/>
        </messageReceivers>
        <operation name="SampleInOnlyOperation" mep="http://www.w3.org/ns/wsdl/in-only">
            <actionMapping>urn:SampleInOnlyOperation</actionMapping>
        </operation>
        <operation name="SampleInOutOperation" mep="http://www.w3.org/ns/wsdl/in-out">
            <actionMapping>urn:SampleInOutOperation</actionMapping>
        </operation>
    </service>
</serviceGroup>

Services.xml file contains available services for a given service group. Then it describes message receivers to be used for each MEP, and the operations and their MEPs and action mappings.

Service deployment

This service can be deployed as a normal service using a service archive (.aar) file. Although this service can be deployed at a repository folder in an Axis2 standard distribution, or a war distribution deployed in a servlet container, this sample shows a way to start a simple htttp server within the IDE it self. This makes it easy to debug the code.

public class TestServer {

    public static final String AXIS2_CONF = "conf/axis2.xml";
    public static final String AXIS2_REPOSITORY = "repository";

    public void startServer() {
        try {
            // creating a configuration context object
            ConfigurationContext confContext =
                    ConfigurationContextFactory.createConfigurationContextFromFileSystem(
                            AXIS2_REPOSITORY, AXIS2_CONF);

            SimpleHTTPServer simpleHttpServer = new SimpleHTTPServer(confContext, 8080);
            simpleHttpServer.start();

            System.out.println("Server started on port 8080 ");
            try {
                Thread.sleep(2000000);
            } catch (InterruptedException e) {
            }
        } catch (AxisFault axisFault) {
            axisFault.printStackTrace();
        }

    }

    public static void main(String[] args) {
        new TestServer().startServer();
    }
}

This main program starts the simpleHttpServer and deploys services and modules given in the repository folder.

Client program

Service client API can be used to access any Web service using Axis2. However, the users has to create the correct XML message to pass on to the service. For this sample, since there is no such format restriction for the request message, an arbitrary message can be sent to verify the service.

public class SampleClient {

    private void invokeService() {
        try {
            ServiceClient serviceClient = new ServiceClient();

            serviceClient.setTargetEPR(new EndpointReference("http://localhost:8088/axis2/services/SampleService"));

            // invoking in only operation
            serviceClient.getOptions().setAction("urn:SampleInOnlyOperation");
            serviceClient.fireAndForget(getOMElement());

            // invoking in out operation
            serviceClient.getOptions().setAction("urn:SampleInOutOperation");
            OMElement response = serviceClient.sendReceive(getOMElement());

            System.out.println("Response ==> " + response.toString());

        } catch (AxisFault axisFault) {
            axisFault.printStackTrace();
        }
    }

    private OMElement getOMElement() {
        OMFactory omFactory = OMAbstractFactory.getOMFactory();
        OMNamespace omNamespace = omFactory.createOMNamespace("http://sample.api", "ns1");
        OMElement omElement = omFactory.createOMElement("Request", omNamespace);
        omElement.setText("Hello service");
        return omElement;
    }

    public static void main(String[] args) {
         new SampleClient().invokeService();
    }
}

Service client API provides two methods called sendReceive and fireAndForget to invoke the in out and in only operations, respectively. Upon running this sample, it can be seen that request and response messages are printed in the respective consoles.

In the above sample, the service is deployed using the .aar file. In fact Axis2 uses the service deployer to deploy .aar files. Therefore, it is possible to develop a custom deployer to do the same thing with a different file format. Next sample describes this.

Custom Deployers

This shows a possible alternative to create a service similar to the earlier, using a new service file format and a custom deployer to read it. This does not mean custom deployers should always be used to deploy new types of services. As it can be seen later, any custom deployer receives the configuration context object at the init method. As a result of that it can be used for any thing. More details about interface can be found here[4].

New service file

<service name="SampleService">
    <operation name="SampleInOnlyOperation" type="inOnly" action="urn:SampleInOnlyOperation"/>
    <operation name="SampleInOutOperation" type="inOut" action="urn:SampleInOutOperation"/>
</service>

This file contains all information just as in the previous services.xml, but in a different format.

Writing the deployer

public class SampleDeployer implements Deployer {

    private ConfigurationContext configurationContext;
    private String serviceName;

    public void init(ConfigurationContext configurationContext) {
        this.configurationContext = configurationContext;
    }

    public void deploy(DeploymentFileData deploymentFileData) throws DeploymentException {
        System.out.println("Deploying the service");
        try {
            XMLStreamReader xmlStreamReader =
                    StAXUtils.createXMLStreamReader(new FileInputStream(deploymentFileData.getFile()));
            OMElement omElement = new StAXOMBuilder(xmlStreamReader).getDocumentElement();
            this.serviceName = omElement.getAttributeValue(new QName("","name"));
            AxisService axisServce = new AxisService(this.serviceName);
            OMElement operationElement = null;
            String type = null;
            AxisOperation axisOperation;
            for (Iterator iter = omElement.getChildElements();iter.hasNext();){
                operationElement = (OMElement) iter.next();
                type = operationElement.getAttributeValue(new QName("","type"));
                if (type.equals("inOnly")){
                    axisOperation =
                            new InOnlyAxisOperation(new QName("",operationElement.getAttributeValue(new QName("","name"))));
                    axisOperation.setMessageReceiver(new SampleInOnlyMessageReceiver());
                } else {
                    axisOperation =
                            new InOutAxisOperation(new QName("",operationElement.getAttributeValue(new QName("","name"))));
                    axisOperation.setMessageReceiver(new SampleInOutMessageReceiver());
                }
                axisOperation.setSoapAction(operationElement.getAttributeValue(new QName("","action")));
                axisServce.addOperation(axisOperation);
            }
            this.configurationContext.getAxisConfiguration().addService(axisServce);

        } catch (XMLStreamException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (AxisFault axisFault) {
            axisFault.printStackTrace();
        }
    }

    public void setDirectory(String string) {

    }

    public void setExtension(String string) {

    }

    public void unDeploy(String string) throws DeploymentException {
        System.out.println("Undeploy the service");
        try {
            this.configurationContext.getAxisConfiguration().removeService(this.serviceName);
        } catch (AxisFault axisFault) {
            axisFault.printStackTrace(); 
        }
    }
}

First, at the init method, it stores the configuration context to be used at the deployment time. Axis2 wraps service file data in a 'DeploymentFileData' object and passes them over to the deploy method. Typically, in this method new objects are added to configuration context using the details of the file descriptor. Here, it reads the service name and operation details from the file, and creates an Axis service object and adds it to Axis configuration. It is worth to note that the corresponding message receivers are set to Axis operations to receive messages.

Registering the deployer

Custom deployers must be registered at the axis2.xml giving an extension to the files and a folder. Following entry can be added to axis2.xml to register above deployer.

<deployer extension=".xml" directory="sample" class="sample.deployer.SampleDeployer"/>

The test server code used in the earlier sample can also be used to deploy this deployer. The new service file should be put into a directory called 'sample' under the repository folder, and with a file name with the extension of .xml.

Finally, this service can be invoked with the sample client used at the first senario. This means the same kind of service has been created with a custom deployer.

Axis2 Modules

In the Axis2 XML processing model different handlers are used to process incoming and out going SOAP messages. Axis2 modules are used to put these handlers to the Axis engine. Typically, these handlers add SOAP headers and processes SOAP header information. This sample shows a method to add handlers, that would add SOAP headers to request and response messages.

Sample module file

public class SampleModule implements Module {
    public void init(ConfigurationContext configurationContext, AxisModule axisModule) throws AxisFault {
        System.out.println("Initializing the module");
    }

    public void engageNotify(AxisDescription axisDescription) throws AxisFault {
        if (axisDescription instanceof AxisService){
            AxisService axisService = (AxisService) axisDescription;
            System.out.println("Engaged to the service ==> " + axisService.getName());
        } else if (axisDescription instanceof AxisOperation){
            AxisOperation axisOperation = (AxisOperation) axisDescription;
            System.out.println("Engaged to the operation ==> " + axisOperation.getName().getLocalPart());
        }

    }

    public boolean canSupportAssertion(Assertion assertion) {
        return false;
    }

    public void applyPolicy(Policy policy, AxisDescription axisDescription) throws AxisFault {

    }

    public void shutdown(ConfigurationContext configurationContext) throws AxisFault {

    }
}

Module class is an optional class in an Axis2 module. Module class can be used to do the initialization setup, if required for a module, and store them in the configuration context to be used by the handlers. EngangeNotify method is called whenever this module is engaged to a service or an operation.

In flow and out flow handlers

Axis2 engine has four types of flows namely inflow, outflow, fault inflow and fault outflow. Each flow contains phases and phases contains handlers. Phases are declared at axis2.xml and handlers can either be declared at axis2.xml or module.xml files. More information about the flows and phases can be found here[3][5][6]. Following two handlers can be used to add SOAP headers to request and response messages.

public class SampleInFlowHandler extends AbstractHandler {
    public InvocationResponse invoke(MessageContext messageContext) throws AxisFault {
        // lets add an Soap header for the out going message
        SOAPEnvelope soapEnvelope = messageContext.getEnvelope();

        if (soapEnvelope.getHeader() == null) {
            String soapNamespace = soapEnvelope.getNamespace().getNamespaceURI();
            // creating a soap factory according the the soap namespce uri
            SOAPFactory soapFactory = null;
            if (soapNamespace.equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI)) {
                soapFactory = OMAbstractFactory.getSOAP11Factory();
            } else if (soapNamespace.equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)) {
                soapFactory = OMAbstractFactory.getSOAP12Factory();
            } else {
                System.out.println("Unknow soap message");
            }
            soapFactory.createSOAPHeader(soapEnvelope);
        }

        OMNamespace omNamespace = OMAbstractFactory.getOMFactory().createOMNamespace("http://sample.module", "ns1");
        SOAPHeaderBlock soapHeaderBlock = soapEnvelope.getHeader().addHeaderBlock("SampleInHeader", omNamespace);
        soapHeaderBlock.setText("Test Header");
        return InvocationResponse.CONTINUE;
    }
}
public class SampleOutFlowHandler extends AbstractHandler {
    public InvocationResponse invoke(MessageContext messageContext) throws AxisFault {
        // lets add an Soap header for the out going message
        SOAPEnvelope soapEnvelope = messageContext.getEnvelope();

        if (soapEnvelope.getHeader() == null) {
            String soapNamespace = soapEnvelope.getNamespace().getNamespaceURI();
            // creating a soap factory according the the soap namespce uri
            SOAPFactory soapFactory = null;
            if (soapNamespace.equals(SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI)) {
                soapFactory = OMAbstractFactory.getSOAP11Factory();
            } else if (soapNamespace.equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)) {
                soapFactory = OMAbstractFactory.getSOAP12Factory();
            } else {
                System.out.println("Unknow soap message");
            }
            soapFactory.createSOAPHeader(soapEnvelope);
        }

        OMNamespace omNamespace = OMAbstractFactory.getOMFactory().createOMNamespace("http://sample.module","ns1");
        SOAPHeaderBlock soapHeaderBlock = soapEnvelope.getHeader().addHeaderBlock("SampleOutHeader",omNamespace);
        soapHeaderBlock.setText("Test Header");

        return InvocationResponse.CONTINUE;
    }
}

Adding a new phase to axis2.xml

The above handlers can either be added to existing phases or a new phase can be added to place these handlers. As described earlier, phases are added to flows. Axis2.xml defines those phases and the new phase can be added by using the following entry.

<phase name="SamplePhase"/>

This entry can be added to user-defined phases sections in each flow.

Module descriptor file

<module name="sampleModule" class="sample.module.SampleModule">

    <Description>This is a sample module</Description>

    <InFlow>
        <handler name="SampleInFlowHandler" class="sample.module.handler.SampleInFlowHandler">
            <order phase="SamplePhase" phaseFirst="true"/>
        </handler>
    </InFlow>

    <OutFlow>
        <handler name="SampleOutFlowHandler" class="sample.module.handler.SampleOutFlowHandler">
            <order phase="SamplePhase" phaseFirst="true"/>
        </handler>
    </OutFlow>
</module>

Module.xml file is used to specify the handlers and its locations. First, it has declared the module class using class attribute. Then, under each flow it has defined the relevant handlers. A handler belongs to a phase, and this phase is given at the order element. If there are more than one handler for a particular phase, then handlers can be ordered within a phase using phase rules. More details about the phase rules can be found here[6].

Now, this module can be deployed under the modules folder by creating module archive (.mar) file.

Engaging module to the service

Any module can be engaged to a service by adding a module reference.

<module ref="sampleModule"/>

Finally, similar service and a client can be used to test this module as given in the first sample.

Summary

Axis2 is an extensively used Web services framework, which can easily be customized for any need. Axis2 modules and custom deployers are two such methods widely being used. This article describes concepts behind these customizations and provide necessary samples to demonstrate the usage.

References

[1]Axis2 Architecture Guide

[2]Axis2 Information Model

[3]Axis2 SOAP Processing Model

[4]Axis2 deployment – Custom deployers

[5]Handler and Phase in Apache Axis2

[6]Axis2 Execution Framework

Author

Amila Suriarachchi, Technical Lead, WSO2 Inc.

About Author

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies
  • WSO2 Inc.