WSO2Con 2013 CFP Banner

Stateful Web Services with Axis2

By Amila Suriarachchi
Date: Wed, 22nd Jul, 2009
Level: Intermediate
Reads: 14283 Discuss this article on Stack Overflow
Apache Axis 2 is a leading open source Web service frameworks. In this article, Amila Suriarachchi describes developing stateful Web services application using the framework.
amila's picture
Amila Suriarachchi
Architect, Member, Management Committee - Data Technologies
WSO2 Inc.

Introduction

Web services are more common place now with the popularity of Service Oriented Architecture (SOA). Conventional Web services are stateless in nature as they use request and response messages for communications, without keeping any state related details.

However, modern Web services applications require the services record communication status. This article describes the options available for developing stateful Web services with Apache Axis2.  Please note that however, since there are no standard specifications for implementing stateful Web services, some techniques used by Apache Axis2 may have problems inter-operating with other Web services stacks available.

Contents

Axis2 supports four types of service scopes. They are request, transport session, SOAP session and application. The request scope describes stateless services while the others describe some session scope depending on the mechanism used. The stateful nature of Axis2 services can be explained with respect to the context object hierarchy. Following is a description of the Axis2 context hierarchy, and an explanation on where the state information is kept. Understanding the context hierarchy is useful when dealing with stateful services using Axis2. 

Axis2 Context Hierarchy

The Axis2 information model consists of two hierarchies, known as description and context. The description hierarchy is used to keep static information, while context hierarchy is used to keep run time information. Context hierarchy consists of the following objects:

  • Configuration context is used as the top-level object. One Axis2 instance has only one configuration context, and is used to hold all system wide properties.
  • Service group context is used to keep service context objects and service group runtime details. Sevices can be grouped in Axis2. Any given service, has an associated service group - even if it is the only service in the group.
  • Service context is used to keep run-time details associated with a service.
  • Operation context is used to keep operation specific properties. This object typically consists of IN/OUT message contexts, depending on the message exchange pattern in use.
  • Message context is used to keep the SOAP message and related properties. A new message context is created for every new message. The service and client developers are given direct access to message contexts through the Axis2 API. Developers are able to access other context objects within the context hierarchy, through message contexts. 

Out of the above contexts, the configuration context is the same for all message context objects, whereas a new message context and an operation context is created for every incoming message irrespective of the service scope used. Therefore, only the service group context and the service context objects differ among the scopes. For example, if the service is deployed in a transport scope, the same service group and service context objects are used for one transport session associated with the service. If the service is deployed in an application scope, the same service group and service context objects are used for all request messages that are targetted for the service.

Service Life Cycle

The Axis2 service life cycle is defined for service implementation objects. The service life cycle interface is as follows:

public interface Lifecycle {
     void init(ServiceContext context) throws AxisFault;
     void destroy(ServiceContext context);
}

As you see, the init method is called when a new service class object is created, and the destroy method when the service class implementation object is removed. The time at which Axis2 creates new objects and removes them, depends on the scope in which the service has been deployed. For example, for request scope services a new object is created for each request, while for transport session services one service class object is created for each transport session. Here, it must be mentioned that although service class objects and service context objects are created and removed at the same time, it is recommended to keep session related properties at the service context objects. This is because the Axis2 clustering module only replicates the properties stored in context objects.

Sample Service

Let's look at some sample code to understand the Axis2 scope concept, and the underlying mechanisms it uses to achieve this. The following service class can be used to demonstrate the different behaviors of the service at different scopes:

package sample.session.service;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.service.Lifecycle;

public class SampleSessionService implements Lifecycle {

    public static final String COUNT = "count";

    public void init(ServiceContext serviceContext) {
        System.out.println("Initializing the service context");
        // initialize the count to zero
        serviceContext.setProperty(COUNT, new Integer(0));
    }

    public void destroy(ServiceContext serviceContext) {
        System.out.println("Destroying the service context");
    }

    public int getCount() {
        ServiceContext serviceContext = MessageContext.getCurrentMessageContext().getServiceContext();
        Integer storedVaue = (Integer) serviceContext.getProperty(COUNT);
        int newCount = storedVaue.intValue() + 1;
        serviceContext.setProperty(COUNT, new Integer(newCount));
        return newCount;
    }
}

The above class implements the life cycle interface by writing console messages to track the place and time it initializes and destroys. The getCount method accesses the serviceContext object and increments the count by one. Therefore, the return value can be used to check whether the two requests belong to the same session or not. The following services.xml file can be used to test each scope by simply changing the value placed in the scope attribute.

<serviceGroup>
    <service name="SampleSessionService" scope="request">
        <messageReceivers>
            <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"
                             class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
            <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
                             class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
        </messageReceivers>
        <parameter name="ServiceClass">sample.session.service.SampleSessionService</parameter>
    </service>
</serviceGroup>

This service can be deployed as a normal service archive and the client code can be generated using wsdl2java tool with the command given below:

   sh wsdl2java.sh -uri http://localhost:8080/axis2/services/SampleSessionService?wsdl -s -uw -u  -ns2p http://service.session.sample=sample.session.client.stub -o <out put directory>

The complete service and client source code which has been tested with Axis2 1.4.1 and Apache Tomcat 6.0.18 can be found here.

Request Scope

Request Scope has the smallest scope of Axis2. A service with the request scope is actually a stateless service and hence can be invoked as a normal service.

SampleSessionServiceStub stub = 
          new SampleSessionServiceStub(
          "http://127.0.0.1:8080/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/");
    for (int i = 0; i < 3; i++) {
        System.out.println("Count ==> " + stub.getCount());
    }

The sample output will look like this

Count ==> 1
Count ==> 1
Count ==> 1

The Tomcat console will show the following output

Initializing the service context

Initializing the service context

Initializing the service context

As expected, the above two outcomes prove that there is no session at all. Although the initialization event has occurred, the destroy method has not been called. At the request scope this event does not occur since service group context and service context objects are not put to configuration context. In fact these two methods have no use since method invocation happens only once.

Transport Session Scope

Services deployed within the transport session receives the same service context, service group context and service class objects within the same transport session. To make this happen the messageSession option must be set to true.

SampleSessionServiceStub stub =
                    new SampleSessionServiceStub(
                            "http://localhost:8088/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/");
    stub._getServiceClient().getOptions().setManageSession(true);
    for (int i = 0; i < 3; i++) {
       System.out.println("Count ==> " +  stub.getCount());
    }

The below output can be obtained when running this client:

Count ==> 1
Count ==> 2
Count ==> 3

The Tomcat console shows the following output:

Initializing the service context

The above outputs shows that only one service context and one services object has been created. But unlike in the earlier case, the destroy method has not been called.

Note that, there is a timeout for keeping these service context object created. Axis2 removes these context objects after 30 seconds which can be configured using 'ConfigContextTimeoutInterval' parameter in the axis2.xml.

How sessions are managed

For the transport session, it uses a session cookie which is managed by the servlet container as in any other web application. The first request message looks like this.

POST /axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/ HTTP/1.1

Content-Type: application/soap+xml; charset=UTF-8; action="urn:getCount"

User-Agent: Axis2

Host: 127.0.0.1:8088

Transfer-Encoding: chunked



93

<?xml version='1.0' encoding='UTF-8'?>
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Body />
   </soapenv:Envelope>0

Then the server sets the cookie at the response.

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Set-Cookie: JSESSIONID=1B9596114A993C74147EBC744A42D6D7; Path=/axis2

Content-Type: application/soap+xml; action="urn:getCountResponse";charset=UTF-8

Transfer-Encoding: chunked

Date: Sat, 27 Dec 2008 09:01:25 GMT



10c

<?xml version='1.0' encoding='UTF-8'?>
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Body>
         <ns:getCountResponse xmlns:ns="http://service.session.sample">
            <ns:return>1</ns:return>
         </ns:getCountResponse>
      </soapenv:Body>
   </soapenv:Envelope>0

After that axis2 client add this cookie to subsequent request messages.

POST /axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/ HTTP/1.1

Content-Type: application/soap+xml; charset=UTF-8; action="urn:getCount"

Cookie: JSESSIONID=1B9596114A993C74147EBC744A42D6D7; Path=/axis2

User-Agent: Axis2

Host: 127.0.0.1:8088

Transfer-Encoding: chunked



93

<?xml version='1.0' encoding='UTF-8'?>
   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Body />
   </soapenv:Envelope>0

You can see that Axis2 keeps this session related detail in the HttpSession object it self.

SOAP Session Scope

SOAP session scope is also similar to transport scope, except in the way it manages the session. Soap session uses WS-Addressing reference parameters to correlate the messages belonging to the same session. As in the case of transport session the manageSession option must be set to true for SOAP session as well.

    SampleSessionServiceStub stub =
            new SampleSessionServiceStub(configurationContext, "http://localhost:8088/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/");
    stub._getServiceClient().engageModule("addressing");
    stub._getServiceClient().getOptions().setManageSession(true);
    for (int i = 0; i < 3; i++) {
        System.out.println("Count ==> " + stub.getCount());
    }

The same output as in the transport session can be observed here as well. Context object removal also happens in the same way. However, note that we have engaged the "addressing" mudle in the above code. That along with the setting of the "manage session" option to true, ensures that we make use of SOAP session and not transport session. 

How the session is managed

SOAP session uses a reference parameter called 'ServiceGroupId' to manage the session. The client sends the first request as any other service invocation.

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
         <wsa:To>http://localhost:8088/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/</wsa:To>
         <wsa:MessageID>urn:uuid:80CBCC10EFFA51034F1230369826309</wsa:MessageID>
         <wsa:Action>urn:getCount</wsa:Action>
      </soapenv:Header>
      <soapenv:Body />
   </soapenv:Envelope>

Upon receiving this request server sends back a response with a 'ServiceGroupId' reference parameter.

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
      <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
         <wsa:ReplyTo>
            <wsa:Address>http://www.w3.org/2005/08/addressing/none</wsa:Address>
            <wsa:ReferenceParameters>
               <axis2:ServiceGroupId xmlns:axis2="http://ws.apache.org/namespaces/axis2">urn:uuid:B9AB09FCC14882B1521230369826635</axis2:ServiceGroupId>
            </wsa:ReferenceParameters>
         </wsa:ReplyTo>
         <wsa:MessageID>urn:uuid:B9AB09FCC14882B1521230369826637</wsa:MessageID>
         <wsa:Action>urn:getCountResponse</wsa:Action>
         <wsa:RelatesTo>urn:uuid:80CBCC10EFFA51034F1230369826309</wsa:RelatesTo>
      </soapenv:Header>
      <soapenv:Body>
         <ns:getCountResponse xmlns:ns="http://service.session.sample">
            <ns:return>1</ns:return>
         </ns:getCountResponse>
      </soapenv:Body>
   </soapenv:Envelope>

For subsequent requests, the Axis2 client sets this parameter as a soap header.

   <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
    <soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
        <axis2:ServiceGroupId xmlns:axis2="http://ws.apache.org/namespaces/axis2" wsa:IsReferenceParameter="true">
            urn:uuid:B9AB09FCC14882B1521230369826635</axis2:ServiceGroupId>
        <wsa:To>
            http://localhost:8088/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/&it;/wsa:To>
        <wsa:MessageID>urn:uuid:80CBCC10EFFA51034F1230369826738</wsa:MessageID>
        <wsa:Action>urn:getCount&it;/wsa:Action>
    </soapenv:Header>
    <soapenv:Body/>
  </soapenv:Envelope>

By using this 'ServiceGroupId', in the SOAP header, Axis2 service can correlate the messages.

Application Scope

Application scope has the highest scope in Axis2. This keeps the same service group and service context object as long as the server runs. Therefore application scope does not require any transport level or soap message level correlation. An application scope service can be invoke with a normal client as in the request scope case.

   SampleSessionServiceStub stub =
            new SampleSessionServiceStub("http://localhost:8080/axis2/services/SampleSessionService.SampleSessionServiceHttpSoap12Endpoint/");
    for (int i = 0; i < 3; i++) {
        System.out.println("Count ==> " + stub.getCount());
    }

Running this client gives the following output as expected.

Count ==> 1
Count ==> 2
Count ==> 3

Running this client again gives the following output.

Count ==> 4
Count ==> 5
Count ==> 6

This shows that it keeps the same service context object forever, without retiring it unlike in the other cases. Also, unlike in earlier cases the Tomcat console does not produce any output for service invocations. But there will be a message, 'Initializing the service context', after all the services are deployed. Axis2 initializes the application scope services at the deployment time and removes only when it shuts down. In all other cases, this initialization happens when the first request for the service comes in. 

Summary

This article discussed ways in which Web services can be developed to communicate in a stateful manner within the Apache Axis2 framework. It also explained the internals of Apache Axis2 that helps achieve sateful invocations. Stateful Web services are particularly useful for applications such as shopping carts, where the need exists to maintain session information between related invocations. This is a very versatile feature when developing business applications using Web services.

References

[1]Axis2 Session Management

[2]Axis2 Information Model

[3]How to validate Axis2/Java session handling capability with transportsession scope

[4]Transport Session Management with Axis2

Author

Amila Suriarachchi, Technical Lead, WSO2 Inc.

AttachmentSize
sample.tar410 KB
WSO2Con 2014