Over the past few years, SOAP has become the de facto standard and backbone for enabling inter-application communication. For the most part, SOAP has been successful in realizing the promise of cross-platform, cross-language integration of devices and software applications that need to communicate.
The strength of SOAP is in its simplicity, flexibility, and its universal acceptability. It lays out simple rules and an XML language of communication between devices and software that need to interact. These rules have been established by the World Wide Web Consortium and backed by industry heavyweights such as IBM, Microsoft, BEA, HP, and Sun Microsystems.
Most existing Java or Enterprise JavaBeans (EJB) applications that cater to specific business functions do not support such SOAP-specific XML consumption and generation. When undertaking standards-based integration efforts, these applications need to talk the language of SOAP and XML. Hence, for business applications that provide critical business functions, a need exists for a bridge layer that can consume and generate SOAP-specific XML. Apache Axis is the open source platform that provides the bridge layer between your applications and SOAP-based Web service interactions.
The Apache Axis Java toolkit provides a thin layer of in-direction between the client wishing to speak SOAP and the server wishing to understand SOAP. The beauty of Axis is that the same toolkit can be used at both the client and server. When used by the client, the Axis toolkit serves as an intermediary between native Java and the underlying encoding of information sent to the server in SOAP XML format. When used to enable a server as a Web service, the toolkit's role reverses and serves as an intermediary between underlying SOAP XML encoding and native Java.
Most common SOAP-based Web services implementations are over the HTTP protocol. However, in implementations that require SOAP to be enabled over a protocol more reliable than HTTP, message-oriented middleware (MOM) is an obvious choice. In addition, the features of commercially available MOM, such as guaranteed message delivery, transactional support, encryption, high performance, and high availability, make the case for combining SOAP and MOM more compelling and appealing.
Axis implementation for HTTP-based SOAP is straightforward. However, enabling Axis over MOM has not always been easy. In this article, I examine key components that come bundled in Axis and introduce additional components that are needed to let Axis work with MOM.
Please refer to resources Resources for the basics on SOAP and Axis.
Benefits of MOM
MOM-based Web services are not suitable for all types of applications. Ideally, they are suited for interaction between applications within an organization or between organizations that require SOA (service-oriented architecture) implementations with service-based consumer and provider paradigms. For such implementations, a MOM-based architecture offers the following benefits:
- For applications that do not require immediate response from the service consumer and have long-running service provider operations, the service consumer and provider can run on different threads via one way calls. Such calls allow consumers to place a service call and not wait for a response back from the service provider. The second benefit is that the service provider does not need to be available at the time the call is placed by the service consumer because MOM provides a store-and-forward mechanism that stores the call message until it is picked up by the service consumer.
- Service providers can scale horizontally by adding more servers for listening to the same MOM queues. The service consumer will remain completely agnostic of such changes at the server.
- Applications requiring guaranteed delivery can rely on the persistent capabilities of MOM, which ensure message delivery in the event of hardware and software crashes.
Axis and asynchronous Web services architecture
In this article, we use Axis as the plumbing for SOAP Web services at both the client and the server. Thus, Axis is responsible for processing information that flows between the client and the server. Let's take an example of a stock-quote Web service to understand how Axis can be enabled over MOM. Our stock-quote Web service provides a stock price for a stock symbol. It has a single function, getStockPrice(String stockSymbol)
, that accepts a stock symbol string as a parameter and returns back a price string. Communication of the stock-quote Web service between the client and server is completed by Axis via MOM. Various types of MOMs, such as IBM's MQSeries or Sonic Software's SonicMQ, can be used. The only MOM requirement is that queues created in these MOMs should be accessible in an enterprise Java application server via Java Message Service (JMS). For purposes of our discussion, we assume one such MOM, and I do not go into the specifics of enabling the queue access in MOM. Your enterprise Java application server and MOM documentation serves as the best source for such information.
Let's first understand the importance of each of the components involved in enabling Axis and the MOM-based Web services application before delving into how these components interact to achieve the needed functionality.
Axis over message-oriented middleware. Click on thumbnail to view full-sized image.
As shown in the figure above, StockQuoteServiceProxy
, JMSSender
, JMSReceive
, and the client and server Axis engine are the key components. Let's understand these components and how they interact.
Step 1: Client invokes getQuote on StockQuoteServiceProxy
Let's look at Client
below:
...
public class Client
{
public static void main() throws Exception
{
String stockSymbol = "IBM";
// Initialize proxy
StockQuoteServiceProxy serviceProxy = new StockQuoteServiceProxy();
// Invoke function
String stockQuote = serviceProxy.getQuote(stockSymbol);
System.out.println("Price of " + stockSymbol + " is " + stockSymbol);
}
}
...
As illustrated in the code above, a Java client invokes getQuote(String stockSymbol)
on the StockQuoteServiceProxy
class. The client is completely unaware of the underlying SOAP and MOM plumbing used to enable the service.
Step 2: StockQuoteServiceProxy constructs and invokes the Call object
The StockQuoteService
at the server is a Web service and thus has a WSDL (Web Services Description Language) file that defines the calls that can be made on the service. Axis provides the WSDL2Java tool that generates client-side proxies. Please see Apache Axis WSDL2Java to learn more about that tool. WSDL2Java is used to generate the initial StockQuoteServiceProxy
Java class. Obviously, the generated Java will not contain code that supplies the transport class JMSTranport
to the Axis engine. Thus, we add the following line to modify the generated Java file: call.setTransport(new JMSTransport());
. The StockQuoteServiceProxy
primarily creates and sets appropriate values on the org.apache.axis.client.Call
object. The org.apache.axis.client.Call
object is a JAXRPC (Java API for XML-based Remote Procedure Call) dynamic invocation interface implementation of the Call
interface:
...
import org.apache.axis.client.*;
import org.apache.axis.configuration.FileProvider;
...
public class StockQuoteServiceProxy
{
...
public String getQuote(String stockSymbol) throws Exception
{
try
{
// Initialize client Axis service
Service axisService = new Service(new FileProvider("client-config.wsdd"));
// Create and construct Call object
call = (Call) axisService.createCall();
call.setUseSOAPAction(true);
call.setSOAPActionURI("");
call.setOperationStyle("rpc");
call.setTargetEndpointAddress(new java.net.URL("target end point"));
// Transport is set to JMSTransport, which returns name of chain that handles
// the transport using MOM
call.setTransport(new JMSTransport());
// getQuote() accepts one parameter of type string
call.addParameter(new javax.xml.namespace.QName("", "stockSymbol"),
new javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema", "string"),
java.lang.String.class,
javax.xml.rpc.ParameterMode.IN);
// getQuote() returns a string
call.setReturnType(org.apache.axis.encoding.XMLType.AXIS_STRING);
// Construct the parameter array
Object[] params = new java.lang.Object[] { stockSymbol};
// Finally, invoke the function
String stockQuote = (String) call.invoke(params);
// invokeOneWay can be used for fire and forget calls
//call.invokeOneWay(params)
return stockQuote;
}
catch (Exception exception)
{
...
}
}
}
When getQuote(String stockSymbol)
is invoked on StockQuoteServiceProxy
, it, as shown in the code above, initializes the Axis engine with the client-config.wsdd
configuration file. It is highly recommended that the path of client.wsdd
is externalized. QuoteServiceProxy
then constructs the Call
object. Since the getQuote()
function accepts one parameter and returns a string, appropriate values are set on Call
using addParameter()
and setReturnType()
, respectively. One of the key calls made by StockQuoteServiceProxy
is setTransportType()
. This call sets access to the class used for enabling Axis SOAP messages over MOM. Most of the code in StockQuoteServiceProxy
is auto-generated by WSDL2Java; however, it does not set the transport type, so you will need to add setTransportType()
. Also, if your application needs only one-way, fire- and forget-type calls by the client, invoke()
will need to be changed to invokeOneWay()
.
The value returned by invoke()
is passed by StockQuoteServiceProxy
to the client.
Step 3: The Axis client engine locates JMSSender
The Axis engine locates JMSSender
via the client-config.wsdd
configuration file. JMSSender
implements the standard Handler
interface, and thus the Axis engine calls the invoke()
function. This code is internal to Axis and not discussed here. Please see Resources for more on Axis internals.
The Axis client, org.apache.axis.client.Service
, is Axis's JAXRPC dynamic invocation interface implementation of the Service
interface. The org.apache.axis.client.Service
comes bundled with the Axis toolkit. It provides the API for the proxy to access the Axis client functionality required to invoke Web services.
The client-config.wsdd
used to initialize the Axis client and JMSTranport
is a simple extension of the org.apache.axis.client.Transport
Axis class. JMSTransport
's constructor sets the transport chain's string name. Axis uses the JMSTransport
class to get the transport chain's name. It then looks up values loaded from client-config.wsdd
to locate the Handler
Java class that is the transport plug-in. For our example, this transport plug-in is JMSSender
, which does the grunt work of communicating with MOM.
The client.wsdd
file is shown below:
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<handler name="JMSSender"
type="java:JMSSender">
</handler>
<transport name="JMSTransport" pivot="JMSSender"/>
</deployment>
The JMSSender
is a transport plug-in for Axis. It implements the org.apache.axis.Handler
Axis interface by extending the org.apache.axis.BasicHandler
class that comes bundled with Axis. JMSSender
contains the code for connecting to MOM queues, setting the names of MOM queues on which the server should return the response, creating the MOM message that contains the SOAP envelope, and dropping the MOM message that contains the SOAP envelope. There after, JMSSender
listens to the MOM queue for the server to respond. On receiving the response MOM message back from the server, it creates the corresponding Axis org.apache.axis.Message
and passes it back to the Axis client.
The JMSSender
relies on the JMS API, making it independent of MOM infrastructure. Each MOM vender, such as MQSeries or SonicMQ, has its own proprietary mechanism for enabling MOM queue access via JMS. To maintain continuity of our discussion, please refer to your MOM and application server documentation to enable MOM to be accessible via JMS.
Step 4: JMSSender locates the MOM queues and places the JMS message on MOM queue
Let's look at JMSSender
:
...
import javax.jms.*;
import javax.naming.*;
import org.apache.axis.*;
import org.apache.axis.handlers.*;
import org.apache.axis.utils.*;
import org.w3c.dom.Element;
public class JMSSender extends BasicHandler
{
...
public void invoke(MessageContext messageContext) throws AxisFault
{
try
{
// Step 4
//Retrieving initial context may vary slightly depending on your J2EE application server.
InitialContext context = new IntialContext();
// Locate JMS objects required for communication with MOM.
QueueConnectionFactory connectionFactory =
(QueueConnectionFactory) context.lookup("Queue Connection Factory JNDI Location");
QueueConnection connection = connectionFactory.createQueueConnection();
QueueSession session =
connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
// Create JMS Queue object and point it to MOM's Request Queue.
Queue requestQueue = (Queue) context.lookup("Request MOM Queue JNDI Location");
//Create JMS Message object that can contain text content.
TextMessage jmsMessage = session.createTextMessage();
//Create JMS Sender object that will be used to send messages on MOM Request Queue.
QueueSender sender = session.createSender(requestQueue);
//Retrieve SOAP envelope from Axis Message.
Message axisMessage = messageContext.getRequestMessage();
SOAPEnvelope soapEnvelope = axisMessage.getSOAPEnvelope();
Element envElement = soapEnvelope.getAsDOM();
String strSOAPBody = XMLUtils.ElementToString(envElement);
//Set the SOAP envelope retrieved in previous step in JMS Message.
TextMessage jmsMessage = session.createTextMessage();
jmsMessage.setText(strSOAPBody);
//Create JMS Queue object and point it to MOM Response Queue.
Queue responseQueue = (Queue) context.lookup("Respnse Queue JNDI Location");
// Create JMS Receiver that listens to MOM Response Queue.
receiver = session.createReceiver(responseQueue);
//Set the JMS Queue object pointing to MOM Response Queue as replyTo on JMS Message.
jmsMessage.setJMSReplyTo(receiveQueue);
//Finally, send MOM Message using JMS Sender.
sender.send(jmsMessage);
// Deliver the message and start the listening by JMS receiver on MOM response queue.
connection.start();
// Step 11
// When response message is sent by server, it is received by receiver.
TextMessage replyMessage = (TextMessage) receiver.receive();
// Create Axis Message and set it in Axis Response Message object.
org.apache.axis.Message responseMessage = new org.apache.axis.Message(replyMessage.getText());
messageContext.setResponseMessage(responseMessage);
//Clean up the objects.
sender.close();
receiver.close();
session.close();
connection.close();
}
catch (Exception exception)
{
// Error handling here.
}
}
...
}
As shown in the code above, the key function that needs to be overridden is invoke()
. The Axis engine calls this function as it processes various Handler
(s); it needs to process the Handler
assigned for handling transport of the SOAP envelope.
The JMSSender
creates JMS QueueConnectionFactory
, QueueConnection
, and QueueSession
objects. It then creates JMS Queue
s that point to the MOM request queue and MOM response queue. JMSSender
then retrieves the SOAP XML envelope from the Axis MessageContext
and places it in the JMS Message
object. QueueSender
then sends the message to the MOM queue.
At this point, JMSSender
halts and waits for the response to come back from service endpoint StockQuoteService
. Steps 5 through 10 detail how the message arrives at the service provider and how the response returns. When the response MOM message arrives on the reply queue, the receiver
that has been listening to the queue retrieves the JMS message. It then extracts the SOAP XML text and sets it on the Axis engine's ResponseMessage
object.
Step 5: MOM transports the MOM queue message containing SOAP envelope
The MOM infrastructure takes over from this point forward and transports the MOM message via its own protocols.
The MOM sender message queue, where the message has been dropped, connects to the receiver message queue. JMSReceiver
listens to the receiver message queue.
The MOM queues should be set up so that the client and server can exchange MOM messages. MOMs generally use their own protocol wire transport. MOMs such as MQSeries and SonicMQ allow the transport of MOM messages over HTTP or HTTPS protocols. Setting up such a mechanism would prove convenient in scenarios where the service client and service provider are within their own firewalls. Please refer to your MOM documentation to set up these queues.
Step 6: The JMS message arrives at the server
The MOM infrastructure transfers the JMS message containing the SOAP envelope to the server. The MOM request queue is enabled to a message-driven bean via an enterprise Java application server.
Steps 7 and 9: The JMSReceiver invokes the Axis engine
Apache Axis comes bundled with SimpleJMSListener
, which implements javax.jms.MessageListener
. Therefore, SimpleJMSListener
can be used to listen for messages arriving on a JMS-compliant MOM. SimpleJMSListener
, however, is a weak implementation of the MessageListener
interface. It is not built to scale and, hence, not recommended for production environments.
To have a scalable and robust solution, I created JMSReceiver
, which is a message-driven bean (MDB) configured in an enterprise Java application server to listen to the JMS-enabled MOM request queue. JMSReceiver
is the bridge between the MOM queues and the Axis engine at the server. Upon receiving the MOM message, JMSReceiver
invokes the Axis server engine.
Let's look at a JMSReceiver
code snippet:
...
import java.rmi.*;
import javax.jms.*;
import org.apache.axis.*;
import org.apache.axis.client.*;
import org.apache.axis.configuration.*;
import org.apache.axis.server.*;
…
public class JMSReceiver implements MessageDrivenBean, MessageListener
{
...
public void onMessage(Message jmsMessage)
{
try
{
// Step 7
// Create an instance of AxisServer with server configuration file.
FileProvider fileProvider = new FileProvider("server-config.wsdd");
Axis Engine axisEngine = new AxisServer(fileProvider);
// Create Axis message from incoming JMS Message.
org.apache.axis.Message soapMessage =
new org.apache.axis.Message(jmsMessage.getText());
// Create a MessageContext and associate with the engine.
MessageContext messageContext = new MessageContext(axisEngine);
// Set the request message in Axis MessageContext.
messageContext.setRequestMessage(soapMessage);
// Invoke AxisEngine. Axis Engine based on SOAP message
// and server-config.wsdd will invoke the StockQuoteService Java class.
// The return values from the Java class are placed in Axis MessageContext.
axisEngine.invoke(messageContext);
// Step 9
// Retrieve the response message from Axis Message Context.
org.apache.axis.Message responseMessage = messageContext.getResponseMessage();
// Get a String representation of response, this string will be placed
// in JMS Message that is sent back to the client.
SOAPEnvelope envelope = responseMessage.getSOAPEnvelope();
Element element = envelope.getAsDOM();
String soapResponse = XMLUtils.ElementToString(element);
// Before we can send JMS message back to the client,
// we need to locate the MOM queue that the
// client specified that response message be sent to.
InitialContext context = new IntialContext();
// Locate JMS objects required for communication with MOM.
Queue responseQueue = (Queue) jmsMessage.getJMSReplyTo();
QueueConnectionFactory connectionFactory =
(QueueConnectionFactory) context.lookup("Queue Connection Factory JNDI Location");
QueueConnection connection = connectionFactory.createQueueConnection();
QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(responseQueue);
TextMessage jmsResponseMessage = session.createTextMessage();
// Now that we have all required JMS objects, we place the response SOAP string
// in the JMS response message.
jmsResponseMessage.setText(soapResponse);
// Finally, send the response back to the on MOM queue that the
// client is expect to receive on.
sender.send(jmsResponseMessage);
}
catch (Exception e)
{
...
// Error handling here.
}
}
}
The key function for the MDB is onMessage()
, which MOM invokes when a message arrives on the queue that JMSReceiver
listens to. Please refer to your enterprise Java application server and MOM documentation to enable MDB listening on MOM queues.
The JMSReceiver
initializes the AxisEngine
via the server-config.wsdd
configuration file. It then retrieves the SOAP envelope from the MOM JMS message and creates an Axis Message
object and places this Message
into the Axis MessageContext
object.
Now when the Axis engine returns the value as described in Step 8 below, the JMSReceiver
retrieves the SOAP response from Axis MessageContext
. It then opens a connection to the MOM JMS response queue based on queue information supplied by the client. This information is retrieved by JMSReceiver
by calling getJMSReplyTo()
on the incoming JMS message. The SOAP response envelope from Axis is then placed in a JMS message and sent to the client via MOM.
Step 8: The Axis server engine invokes the call on StockQuoteService
The org.apache.axis.AxisServer
is the core plumbing provided by Axis. It is the primary object that enables Java/enterprise Java classes for SOAP-based Web services. It is responsible for converting SOAP calls to Java-based calls understandable by service providers such as our StockQuoteService
. The Axis engine is initialized with server-config.wsdd
, which provides information such as the fully qualified name of the class implementing the service, its operations, and corresponding parameters enabled for the Web service. JMSTransport
and corresponding entries in server-config.wsdd
work along similar lines as on the client.
Please refer to server-config.wsdd
to get details on using server-config.wsdd
as a deployment descriptor for the Axis engine.
The server-config.wsdd
file follows:
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
...
<handler name="JMSSender"
type="java:JMSSender">
</handler>
<transport name="JMSTransport" pivot="JMSSender"/>
<service name="StockQuoteService" provider="java:RPC">
<parameter name="className" value="StockQuoteService"/>
<operation xmlns:operNS="http://com.serviceprovider.stockquote " name="getQuote" qname="operNS:getQuote">
<parameter xmlns:tns="http://www.w3.org/2001/XMLSchema" name="" type="tns:string"/>
</operation>
<parameter name="allowedMethods" value="*"/>
</service>
...
</deployment>
The StockQuoteService
class is the service end point. It has one function, getQuote()
; for the simplicity of our example, it has hard-coded values of various quotes. As you can see from the code below, StockQuoteService
is completely agnostic of the Axis plumbing used to enable the class as a SOAP-based Web service:
...
public class StockQuoteService
{
public String getQuote(String stockSymbol)
{
// Hard coded values for simplicity
if (stockSymbols.equals("IBM"))
{
return "45.34";
}
else if (stockSymbols.equals("MSFT"))
{
return "35.34";
}
else if (stockSymbols.equals("GOOG"))
{
return "120.78";
}
else
{
return "0";
}
}
}
Conclusion
In this article, we saw how Web services enabled a store-forward, guaranteed-delivery mechanism. For simplicity's sake, we used Axis at both ends—at the service consumer and at the service provider. This approach, however, is not required. For the brave of heart, one may have a .Net client service consumer and an Axis service provider, and the architecture will stand, for the most part. Every project in the real world has unique dynamics around the choice of technology, tools, and process. Implementing reliable Web services using Apache Axis and MOM may not be suitable for all projects, but can prove to be a good choice when the service provider and consumer want more reliable means of communicating and can agree on communicating via MOM in addition to Web services-based interaction.