Novell Home

Building Asynchronous Web Services with JMS

Novell Cool Solutions: Feature
By J. Jeffrey Hanson

Digg This - Slashdot This

Posted: 2 Jun 2004
 

J. Jeffrey Hanson
Chief Architect
eReinsure.com, Inc.
jeff@jeffhanson.com

This article is the first of a series of articles which outline techniques and technologies that can be used to build Web services and Web service clients that interact asynchronously using JMS in a service-oriented architecture. (Second Article: Connecting Asynchronous Services and Service Clients)

In this article, we will build a framework that can be used to handle SOAP messages asynchronously across an HTTP-based network.

A Brief Introduction to Web Services

The term Web services is used to describe concepts and technologies that integrate business processes and services over the Web using standard network protocols. Web services, as a technology, is a mechanism for delivering cross-platform, cross-language services and business content to any Web-enabled client or device.

Web services are URL-addressable software components that are connected by a common protocol, and which allow applications and services to access them over the Internet. Web services are based on XML envelopes containing either documents or procedures and data structures. The Web services model allows applications to interact with one another in a very loosely-coupled manner over a networked environment.

The operations surrounding Web services usually fall within three major categories: publish, find, and bind. Publishing involves exposing the service in a manner that makes it easy to find and use by a service consumer. This typically involves staging the service in a public registry. When a service is listed in a registry, it can then be found or discovered, bound using SOAP and then invoked by a service consumer.

Figure 1 illustrates the relationships between the parties involved in the lifecycle of a typical Web service.


Figure 1: Parties involved in the lifecycle of a typical Web service

SOAP and HTTP

Since HTTP is ubiquitous throughout the Web infrastructure, it is being presented as the protocol of choice for transporting SOAP request and response data.

A SOAP method can be thought of simply as a SOAP-encoded HTTP POST request. SOAP requests should use the text/xml content-type. A SOAP request over HTTP should indicate the SOAP method to be called using the SOAPAction HTTP header. The SOAPAction header contains an application-specific method name.

A SOAP request is an XML document containing the in and out parameters of the SOAP method. These values are child elements within the SOAP body and share the same namespace as the SOAP method name.

A SOAP response is similar to a SOAP request, in that the content for the response must be contained within the SOAP body and typically uses the same datatypes as the request.

Encoding the Payload

A Web service request can be processed as either RPC-oriented or document-oriented. If a Web service operation is defined in its WSDL file as document-oriented, the request and response for that operation will contain XML documents. If a Web service operation is defined in its WSDL file as RPC-oriented, the request and response for that operation contain request parameters and return values.

Document-oriented requests do not rely on request parameters and return values; instead, they rely on common XML-schemas for interpretation and transformation. This is referred to as literal-encoding.

RPC-oriented requests are, by their very nature, synchronous. However, document-oriented requests are well-suited for asynchronous environments. For example, an XML-based invoice or purchase order can be transferred in its entirety as a document-oriented request with the understanding that any responses will be transmitted later in a separate communication, depending upon the nature of the workflow.

We will rely on document-oriented, literal-encoded SOAP messages for our framework, since this approach easily allows us to wrap our request payloads and response payloads as JMS messages to be processed asynchronously.

Handling SOAP Messages Synchronously

HTTP is the primary protocol used to transmit SOAP messages; therefore we need a mechanism in Java that will handle HTTP for us. This type of framework needs to do three things:

  • Parse SOAP messages from XML and convert them to Java objects and/or method calls (sometimes referred to as "unmarshalling").


  • Invoke the objects responsible for handling the messages.


  • Serialize responses or errors (if either is needed) back into XML (sometimes referred to as "marshalling") and deliver them to the submitters of the requests.

Java HTTP servlets are specifically designed to handle request and response messages transported using the HTTP protocol, therefore servlets are typically the receiving component for a SOAP message when using the Java programming language.

Handling SOAP Messages Asynchronously

As with synchronous messaging, HTTP is again the primary protocol used to transmit our SOAP messages, so we will still be relying on servlets to handle HTTP traffic, however, we must address the issues of message storage, message forwarding, and notification responses. Therefore, in contrast to a synchronous message framework, this type of framework needs to do a few additional things:

  • The client-side portion of the framework must be able to store-and-forward messages reliably in order to guarantee delivery in case of network failure or communication errors.


  • Transport messages to the specific endpoints across an HTTP network.


  • The server-side components must parse SOAP messages from XML and convert them to Java objects and/or method calls (sometimes referred to as "unmarshalling").


  • Invoke the objects responsible for handling the messages.


  • Serialize responses or errors (if either is needed) back into XML (sometimes referred to as "marshalling") and deliver the response messages to a server-side component which will store-and-forward the response messages reliably in order to guarantee delivery in case of network failure or communication errors.


  • Transport the response messages to the submitters of the requests across an HTTP network.

There are two types of models typically employed by clients for sending and receiving asynchronous messages, request-and-poll and request-and-register.

In a request-and-poll model, a sending application or service sends a message to a receiving application or service. Once the message has been sent, the sender spawns a new thread which polls for a response.

In a request-and-register model, the sender registers as an event listener with the receiver or a proxy of the receiver. Once the sender is registered as a listener, the sender sends the message and the receiver transmits a response to the event callback of the sender or a proxy of the sender.

We will be using the request-and-register model for sending and receiving messages in this article and we will employ JMS topics and HTTP proxies to help make it happen.

A Message from Your MOM

Message-Oriented Middleware (MOM) is a category of inter-application communication software that presents an asynchronous message passing model as opposed to a request/response model. Most MOM systems are based around a message queuing system.

The primary advantage of a message-oriented communications protocol is the ability to store, route and resend a message that is to be delivered.

Most MOM systems provide a persistent storage to hold messages until they are successfully transferred. This means that it is not necessary for both the sender and receiver to be connected at the same time. This is useful for dealing with faulty connections, unreliable networks, and timed connections. It also means that if a receiver fails to receive a message for any reason, the sender can continue unaffected, since the messages will be held in the message store and will be transmitted when the receiver reconnects.

MOM systems present two messaging models:

  • Point-to-point - This model is based on message stores known as queues. A sender sends a message to a specified queue. A receiver receives messages from the queue. A queue can have multiple senders and receivers, but an individual message can only be delivered to one receiver. If multiple receivers are listening for messages on a queue, the underlying MOM system usually determines which receiver will receive the next message. If no receivers are listening on the queue, messages remain in the queue until a receiver attaches to the queue.


  • Publish-Subscribe - This model is based on message stores known as topics. Publishers send messages to a topic. Subscribers retrieve messages from a topic. Unlike the point-to-point model, many subscribers can receive the same message.
  • The following diagram illustrates the relations and interactions for sending a message from one publisher to a topic where multiple subscribers are registered to receive the message:


    Figure 2: Interactions between a publisher, some subscribers, and a topic

    JMS Tells MOM

    An increasingly important requirement of J2EE application systems is their ability to handle asynchronous messages passed from MOM systems. Java provides the Java Message Service (JMS) for handling MOM messages.

    JMS is an application programming interface (API) abstraction of common concepts found in all MOM systems. JMS does not define wire protocols or message-content formats.

    Integrating SOAP and HTTP with JMS

    JMS/MOM systems do not always provide the necessary infrastructure for communicating across HTTP and since most security-aware networks are limited to HTTP traffic, we must build the components to integrate the two environments.

    For Web services applications, message senders can publish messages to a local topic where a small, waiting, proxy component will receive the message from the topic. The proxy then wraps the message in a SOAP envelope and transports the message across an HTTP connection. The proxy then waits for the HTTP response. Once the response is received, the proxy publishes it to the local topic. The callback method supplied by the original sender is called and the response is received. This creates an environment in which the message sender can operate in an asynchronous manner.

    On the receiver's side of the interaction, a proxy is waiting for HTTP requests. When an HTTP request is received, the proxy extracts the SOAP envelope, extracts the message from the envelope, and publishes the messages to a local topic. Business-logic components registered as subscribers to the topic receive the message, execute business logic, and publish the response, if any, to the topic. The proxy then receives the message from the topic, wraps the message in a SOAP envelope, and passes it back to the sender as an HTTP response.

    The following diagram illustrates the relationships and interactions between a sender, a receiver, topics and proxies:


    Figure 3: The use of proxies for HTTP messaging

    Pushing and Pulling with Proxies

    Both communication points (request and response) of our framework will need to transmit a request or response across an HTTP-based network. This is accomplished with the use of proxy servlets at both points. The proxy servlets receive the request or response, wrap it as a JMS message and publish it to the appropriate JMS topic.

    The basic premise of the proxy servlet is to use HTTP POST requests for pushing message requests and HTTP GET requests for pulling message responses. The following code illustrates our proxy servlet:

    package com.jeffhanson.ws.async;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Hashtable;
    
    import javax.jms.*;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    
    public class ProxyServlet extends HttpServlet
    {
       private static final String CONN_FACTORY_NAME = "jms/MyConnectionFactory";
       private static final String TOPIC_SUBJECT = "jms/myTopic";
       private static final String CONTEXT_FACTORY_VALUE =
          "com.mycompany.JMSInitialContextFactory";
       private static final String PROVIDER_HOST = "myhostname";
       private static final String PROVIDER_PORT = "5678";
       private static final String PROVIDER_URL_VALUE =
          PROVIDER_HOST + ":" + PROVIDER_PORT;
    
       private TopicConnection topicConnection = null;
       private MessageConsumer consumer = null;
       private TopicSession session = null;
       private TopicConnectionFactory topicConnectionFactory = null;
       private Destination destination = null;
       private Context context = null;
       private MessageProducer producer = null;
    
       /**
        * The standard HttpServlet doGet method is used to
        * "pull" responses
        */
       protected void doGet(HttpServletRequest request,
                            HttpServletResponse response)
          throws ServletException
       {
          try
          {
             Message message = receiveNoWait();
             if (message != null)
             {
                writeMessage(message, request, response);
             }
          }
          catch (IOException e)
          {
             throw new ServletException(e);
          }
          catch (JMSException e)
          {
             throw new ServletException(e);
          }
          catch (NamingException e)
          {
             throw new ServletException(e);
          }
       }
    
       /**
        * The standard HttpServlet doPost method is used to
        * "push" requests
        */
       protected void doPost(HttpServletRequest request,
                             HttpServletResponse response)
          throws ServletException
       {
          try
          {
             Message message = readMessage(request, response);
             if (message != null)
             {
                send(message);
             }
          }
          catch (IOException e)
          {
             throw new ServletException(e);
          }
          catch (JMSException e)
          {
             throw new ServletException(e);
          }
       }
    
       /**
        * The readMessage method reads an HTTP SOAP request and wraps it
        * as a JMS Message.
        */
    
       protected Message readMessage(HttpServletRequest request,
                                     HttpServletResponse response)
         throws IOException, JMSException, ServletException
       {
          StringBuffer buffer = new StringBuffer();
          BufferedReader reader = request.getReader();
          for (String line; (line = reader.readLine()) != null;)
          {
             buffer.append(line);
          }
    
          String text = buffer.toString();
          Message message = null;
          try
          {
             if (text.length() == 0)
             {
                message = getSession().createMessage();
             }
             else
             {
                message = getSession().createTextMessage(text);
             }
          }
          catch (NamingException e)
          {
             throw new ServletException(e.toString());
          }
          catch (JMSException e)
          {
             throw new ServletException(e.toString());
          }
    
          return message;
       }
    
       /**
        * The writeMessage method unwraps a JMS message and returns
        * it to the HTTP client as a SOAP response
        */
       protected void writeMessage(Message message,
                                   HttpServletRequest request,
                                   HttpServletResponse response)
         throws IOException, JMSException, ServletException
       {
          PrintWriter writer = response.getWriter();
          if (message instanceof TextMessage)
          {
             TextMessage textMessage = (TextMessage) message;
             writer.write(textMessage.getText());
          }
       }
    
       /**
        * The send method does the work of publishing
        * messages to the JMS server
        */
       private void send(Message message)
          throws JMSException
       {
          try
          {
             ((TopicPublisher)getProducer()).publish((Topic)getDestination(),
                                                     message);
          }
          catch (NamingException e)
          {
             throw new JMSException(e.toString());
          }
       }
    
       /**
        * The receiveNoWait method does the work of receiving
        * messages from the JMS server
        */
       private Message receiveNoWait()
          throws NamingException, JMSException
       {
          return getConsumer().receiveNoWait();
       }
    
       /**
        * retrieves the JMS message consumer
        */
       private MessageConsumer getConsumer()
          throws JMSException, NamingException
       {
          if (consumer != null)
          {
             return consumer;
          }
    
          boolean ignoreLocalMsgs = false;
          consumer = getSession().createSubscriber((Topic)getDestination(),
                                                   null,
                                                   ignoreLocalMsgs);
          return consumer;
       }
    
       /**
        * retrieves the JMS message producer
        */
       private MessageProducer getProducer()
          throws JMSException, NamingException
       {
          if (producer != null)
          {
             return producer;
          }
    
          producer = getSession().createPublisher((Topic)getDestination());
          return producer;
       }
    
       /**
        * retrieves the JMS topic session
        */
       private TopicSession getSession()
          throws JMSException, NamingException
       {
          if (session != null)
          {
             return session;
          }
    
          session = getConnection().createTopicSession(false,
                                                       Session.AUTO_ACKNOWLEDGE);
          return session;
       }
    
       /**
        * retrieves the JMS topic connection
        */
       private TopicConnection getConnection()
          throws NamingException, JMSException
       {
          if (topicConnection != null)
          {
             return topicConnection;
          }
    
          topicConnection = getConnectionFactory().createTopicConnection();
          return topicConnection;
       }
    
       /**
        * retrieves the JMS topic connection factory
        */
       private TopicConnectionFactory getConnectionFactory()
          throws NamingException
       {
          if (topicConnectionFactory != null)
          {
             return topicConnectionFactory;
          }
    
          topicConnectionFactory =
             (TopicConnectionFactory)getContext().lookup(CONN_FACTORY_NAME);
          return topicConnectionFactory;
       }
    
       /**
        * retrieves the JMS topic/destination
        */
       private Destination getDestination()
          throws NamingException
       {
          if (destination != null)
          {
             return destination;
          }
    
          destination = (Destination) getContext().lookup(TOPIC_SUBJECT);
          return destination;
       }
    
       /**
        * retrieves the JNDI context of our JMS server
        */
       private Context getContext() throws NamingException
       {
          if (context != null)
          {
             return context;
          }
    
          Hashtable env = new Hashtable();
          env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY_VALUE);
          env.put(Context.PROVIDER_URL, PROVIDER_URL_VALUE);
          env.put(Context.SECURITY_PRINCIPAL, "jdoe");
          env.put(Context.SECURITY_CREDENTIALS, "foobar");
    
          context = new InitialContext();
          return context;
       }
    }
    Conclusion

    JMS systems do not always provide the necessary infrastructure for communicating messages across HTTP, however with the power of servlets; it is not very complicated to build the components to integrate the two environments.

    In this article we have outlined the techniques and technologies that can be used to build Web services and Web service clients that interact asynchronously using JMS in an HTTP-based network.

    In our next article, we will extend our framework to demonstrate JMS clients and loosely-coupled, JMS-aware, business-logic components which realize the benefits of a service-oriented architecture.

    For Additional Information

    For more information about the technologies discussed in this Cool Solutions for Developers article, refer to the following resources:

  • For more information about SOAP, visit http://www.w3.org/TR/SOAP.
  • For more information about JMS, visit http://java.sun.com/jms.
  • For Novell-specific Java programming information, see http://developer.novell.com/ndk.
  • For information about eReinsure.com, Inc. and its J2EE-based applications and Web services for the reinsurance industry, visit http://www.ereinsure.com.

  • Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

    © 2014 Novell