Parameswaran Seshan

Subscribe to Parameswaran Seshan: eMailAlertsEmail Alerts
Get Parameswaran Seshan: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Apache Web Server Journal, Java Developer Magazine

Apache Web Server: Article

Java Feature — A Generic JMS Listener for Apache Axis 1.x

A needed transport-level handler

Unlike the HTTP protocol there's no stable default JMS listener for invoking the Web Services exposed in Apache Axis 1.x using JMS (Java Message Service) as the transport protocol - other than the one provided merely for demo purposes.

This article describes a fully working generic JMS listener that can act as a JMS transport receiver handler for Axis and allow service clients to uniquely address individual Web Services in a JMS way and invoke them over JMS.

Apache Axis is a popular Java-based Open Source platform for exposing Web Services. It has native support for handling invocations into Web Services based on the SOAP (Simple Object Access Protocol) application protocol. By default, the Axis server supports HTTP as the protocol for transporting the SOAP payload and provides an HTTP transport listener to do the same on its own. The HTTP transport listener accepts the SOAP requests coming over HTTP and then hands off the SOAP payload to the Axis engine for application-level handling of the request (like SOAP parsing, extracting input parameters, invoking the right service implementation, etc.), and gets the response SOAP message from the Axis engine and sends it back to the caller in the HTTP response.

For those enterprise applications where reliable invocation and guaranteed delivery of invocation messages are important, JMS, rather than HTTP, is the preferred protocol for transporting SOAP messages. JMS implementation providers, with built-in reliability features like re-try mechanisms, ensure that messages reach the message consumer application whatever the case. JMS is also the best way to handle asynchronous invocations of Web Services.

However, what Axis 1.x provides for JMS transport protocol use is only a basic demo listener that's not really meant for production-level use. This listener is also not easy to use and isn't flexible enough to be able to specify and handle unique endpoint addresses for each individual Web Service exposed. So, for client applications that need to invoke the Axis Web Services over JMS a flexible, stable, and easy-to-use JMS transport listener and handler is required.

This article implements a generic JMS listener and describes how it is to be used along with the Axis server. For purposes of this paper, I've considered the Open Source frameworks Apache Axis 1.2 and JMS provider OpenJMS 0.7.6.1. However, this should be largely applicable to the higher versions of Axis in the 1.x series too.

The Addressing Model
In an Axis server, each Web Service is described in the server-config.wsdd file and the service name mentioned there becomes part of the concrete HTTP URL (concrete port binding) for accessing the Web Service. For example, to access a "StockQuoteService" defined in the server-config.wsdd of the Axis server running in host www.samplehost.com, the default HTTP URL would be www.samplehost.com/axis/Services/StockQuoteService. Each Web Service similarly defined in server-config.wsdd will have a unique access URL like the one above with the first portion of the URL, i.e., http://www.samplehost.com/axis/Services/, remaining the same. In this sense, each Web Service will be addressed with a unique HTTP destination.

We can choose to follow a similar model for exposing the same Web Services over the JMS transport protocol. In other words, each service endpoint will be available at a unique JMS destination (aka a queue). So for each Web Service defined in the Axis server, we define a separate queue in the JMS provider - in our case OpenJMS - and update the openjms.xml file in the config folder of the OpenJMS home for defining one queue for each Axis-deployed Web Service that's meant to be accessible over JMS. In this article, the approach taken is to use the service name defined in Axis itself as the queue name (similar to the HTTP concrete binding mentioned above). For example, for the sample Web Service called "MessageService" provided in the Axis distribution, we can define a queue with the same name putting the entry <AdministeredQueue name="MessageService"/> in openjms.xml.

This addressing model applies uniformly to both RPC-style and message-style Axis services. It's more straightforward and standards-compatible with the WSDL specs. The JMS destination for each Web Service becomes the address in the concrete port binding for the service and the Web Service clients can directly use this concrete JMS destination mentioned in the WSDL file for invoking the service.

This is a better model than the non-standard way of specifying the Axis Web Service name as the prefix in the request SOAP message body's first XML element. For example, for the invocation of the method "getQuote" in the Axis sample Web Service named "urn:xmltoday-delayed-quotes," which is an RPC-style service, the basic JMS listener provided in the Axis distribution expects the client to create the SOAP body element <urn:xmltoday-delayed-quotes:getQuote> containing the service name as the XML prefix.

The client does this through code similar to call.setOperationName(new QName("urn:xmltoday-delayed-quotes:getQuote", "getQuote")). This fact doesn't appear in the WSDL definition of the service hence interoperability with different external service clients could become an issue. It's best to stick to the details given in the WSDL file and with the new service endpoint addressing scheme introduced here, all the clients can, in a standard way, just keep the first SOAP body element as <getQuote> following the WSDL details alone, thereby improving interoperability.

The Role of JMS Listener
The JMS listener is a server-side component that needs to listen to incoming JMS messages containing SOAP messages at the defined JMS queue. These SOAP messages are request messages coming from service clients that are trying to invoke the Web Service(s). Once the JMS message is received, the onMessage() method in the listener needs to get the message content which is the SOAP XML payload, and invoke the Axis engine supplying the SOAP XML message and also specifying to Axis the name of Web Service that's being invoked. This handing over of the SOAP message to Axis server is the key responsibility of this listener.

Then, if the client expects a response back from the Web Service (such as in a pseudo-synchronous call), the JMS listener needs to get the SOAP response message from the Web Service and put it as the payload in a new JMS message and send this message to the JMS queue destination the client is waiting on. This JMS listener can be used for receiving requests of both RPC-style and document-style Web Services invocations since it doesn't read and interpret the SOAP message at all; it just sticks to its role as a transport-level handler.

Implementing the Listener
Now we'll look at the implementation of the JMS listener. To realize the addressing model described above, the approach is to have one instance of the listener class GenericJMSSOAPListenerForAxis for each Axis Web Service. This class that implements javax.jms.MessageListener is a generic listener for JMS Web Service requests. I'll explain the salient parts of this class in this section and the next. The full source code of this and other classes discussed here is available for download in the resources section.

First the constructor of this class needs to register with OpenJMS for receiving messages in the queue defined for this Web Service. The constructor takes the Web Service name as an input argument. A static initialization block is used to instantiate the Axis engine and this gets executed when the Java Virtual Machine (JVM) loads the GenericJMSSOAPListenerForAxis. All the instances of this class have to use the same Axis engine instance.

...
public GenericJMSSOAPListenerForAxis(String webserviceName)
{
    ...
    this.webserviceName = webserviceName;
    ...
}

AxisJMSListenersStarter class's main method starts the listeners by reading the XML file "jmswebsvcs.xml" that contains the list of Axis Web Services, creating and starting one instance of GenericJMSSOAPListenerForAxis for each service in the list by passing the name of the Web Service as an argument to the constructor. In effect this dynamically creates the concrete service endpoint destinations for the JMS protocol, since each Web Service now gets a unique concrete address.

public static void main(String[] args)
{
   // Read the JMS Web Services names from the xml file.
   ...
     org.w3c.dom.Document servicesListDoc = db.parse(inFileFullPath);
     org.w3c.dom.NodeList servicesList = servicesListDoc.getDocumentElement().getElementsByTagName("service");

   for (int k = 0; k < servicesList.getLength(); k++)
   {
   String webSvcName = ((org.w3c.dom.Element) servicesList.item(k)).getFirstChild().getNodeValue();
   new GenericJMSSOAPListenerForAxis(webSvcName);
   }
...

Now let's look at the message-handling logic in the GenericJMSSOAPListenerForAxis that's instantiated for a particular Web Service, say, "MessageService." Its onMessage() method is called once the message arrives in the queue named "MessageService." After creating the Axis MessageContext for the message the onMessage method sets the serviceHandler field of the MessageContext to tell Axis that the Web Service being invoked is "MessageService" and that for executing service-specific functionality, the service implementation class, as defined in the server-config.wsdd, for service name "MessageService" should be invoked.

public void onMessage(javax.jms.Message inMsg)
{
    ...
    axisMsgCtxt.setRequestMessage(axisSoapMessage);
    // Set the target Web Service in the Axis message context to indicate that this message should
    // go to the Axis webservice named <webserviceName> for which this queue is receiving messages.
    axisMsgCtxt.setTargetService(webserviceName);
...
}

This method then sends the response SOAP message to the JMS client. However, at this point no correlation id is used. For simplicity's sake it's assumed here that the client, after sending the message, waits on a receive queue expecting to get a response message for the Web Service invocation it just made. This listener class can easily be extended to refer to and use a client-specified JMS correlation id to send a correlated response to the client.

Invoker-side implementation
Now let's look at some key aspects on the client side of the invocation. The classes written for the client side are JMSTestClientRPC, MyJMSTransportForAxis, JMSTestClientMessageStyle - which are client classes that invoke RPC- and message-style services respectively, for example they respectively invoke the services "urn:xmltoday-delayed-quotes" of samples.stock and "MessageService" of samples.message packages, the samples available in the Axis distribution - and MyJMSSender - which are the JMS transport handlers for the client side. JMSTestClientRPC makes a call using the Axis Call object to a given RPC Web Service using JMS as the transport. Hence it specifies the unique target endpoint JMS address defined for that particular Web Service, for example, here "urn:xmltoday-delayed-quotes." Please note that the RPC operation name is set with just the method name as given in the WSDL file and no service name prefix. The Axis Call object has to be told that a JMS transport handler needs to be used for this invocation. This handler class is instantiated and attached to the call here. If this value isn't set, Axis will use the HTTP transport handler by default. For both RPC- and message-style invocation, the same MyJMSTransportForAxis and MyJMSSender classes are used and connected via an entry made in the client-config.wsdd file available in the client classpath. MyJMSTransportForAxis class helps Axis locate this entry in the client-config.wsdd. MyJMSSender's job is to actually send the SOAP message as a JMS message to the Web Service queue destination specified here and get the response from the response queue.

...String webSvcJMSDestination = "urn:xmltoday-delayed-quotes";
axisCall.setProperty(org.apache.axis.transport.jms.JMSConstants.DESTINATION, webSvcJMSDestination);
axisCall.setOperationName("getQuote");
...org.apache.axis.client.Transport transport = new MyJMSTransportForAxis();
axisCall.setTransport(transport);
...


More Stories By Parameswaran Seshan

Parameswaran Seshan performs the role of an independent educator/trainer, architect, researcher, and architecture consultant, in Information Technology (IT). He teaches architecture, design and technology related courses. Prior to this, he worked as Principal (Education and Research) with E-Comm Research Lab, Infosys Technologies Limited, Bangalore, India. He has more than 15 years of work experience in the IT industry, involving teaching, architecture, research, and programming. His areas of interest include Enterprise Architecture, Process-centric architecture, Intelligent software systems, Intelligent agents, software architecture, Business Process Management systems, Web services and Java. You can reach Parameswaran at, contact {at} bitsintune [dot] com.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.