Architect: SOA
   DOWNLOAD
 Oracle SOA Suite
   TAGS
soa, esb, All

Using Request-Response SOAP over JMS Web Services

Using SOAP over JMS transport can be more scalable and efficient than over HTTP; here are some tips to get started.

By Bob Murphy

Published September 2008

Managing SOAP messages over the Java Message Service (JMS) API can provide greater scalability and reliability than HTTP/S. The greater efficiency derives from the fact that JMS transports and stores Web service requests and response messages in a queue until they can be processed by the server and the client frees up threads and other resources on both systems.

A Web service configured to use the JMS transport communicates with its client through a JMS queue. The client sends a SOAP message to the queue and waits for a response message on a temporary queue created just for the JMS session. The Web service processes the message and sends the response back to the temporary queue.

The Java code for the client and service don't change when SOAP over JMS is configured as the transport protocol. In fact, a Web service can support SOAP over JMS and SOAP over HTTP at the same time. The difference between using these protocols is apparent in how the Web service endpoints are defined.

In this article, you will learn tips for using SOAP over JMS transport, using Oracle Service Bus (formerly BEA AquaLogic Service Bus) as the message backbone. There isn't yet a standard defined for the SOAP over JMS transport and this article will also help if you need to integrate with other vendor implementations.

Example

The code sample below shows a Web service configured to support both the JMS and HTTP transports. The WLJmsTransport annotation configures the JMS transport in the web service and its use is shown in bold. In addition to the service URI and port name, the JNDI name of the JMS queue and connection factory are also defined. These items are optional and if not defined the connection factory defaults to weblogic.jms.ConnectionFactory and the queue defaults to weblogic.wsse.DefaultQueue.

package example;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

import weblogic.jws.WLHttpTransport;
                               
import weblogic.jws.WLJmsTransport;

import com.accounts.AccountData;
import com.accounts.accountsXMLSchema.AccountDocument;
import com.accounts.accountsXMLSchema.AccountNameDocument;
import com.accounts.accountsXMLSchema.AccountType;

@WebService(name="AccountsPortType", 
                serviceName="AccountsWebService",
                targetNamespace="http://www.accounts.com/AccountsService")

@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,
        use=SOAPBinding.Use.LITERAL,
        parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)

@WLHttpTransport(
                serviceUri="Accounts",
                portName="AccountsPort")
                               
@WLJmsTransport(
                serviceUri="AccountsJMS",
                portName="AccountsJMSPort",
                connectionFactory=�apps.service.cf�, 
                queue="apps.service.queue")


public class AccountsWebService {

        @WebMethod(operationName = "create")
        @WebResult(name="Account")
        public AccountDocument createAccount(
                        @WebParam(name="AccountName")
                        AccountNameDocument accountNameDoc) throws Exception {
                AccountDocument doc = 
         AccountDocument.Factory.newInstance();
                AccountType account = doc.addNewAccount();
                account.setAccountName(accountNameDoc.getAccountName());
                account.setAccountId(1001);
                return doc;
        }
}                       
                            

The WSDL

Web Service Definition Language (WSDL) describes the Web service so that clients can send requests and receive responses. The binding and service sections of the WSDL contain the specifics defined for the transport protocol. For SOAP over JMS, the binding transport is defined as http://www.openuri.org/2002/04/soap/jms/ and the service's endpoint address includes the JMS queue and connection factory's JNDI names; the protocol is jms rather than http. For example, the endpoint address for the Web service shown above is jms://localhost:7020/AccountsWS/AccountsJMS?URI=apps.service.queue&FACTORY=apps.service.cf.

Here are the WSDL excerpts highlighting the SOAP over JMS transport:

<s0:binding name="AccountsWebServiceSoapBindingjms" 
   type="s1:AccountsPortType">
      <s2:binding style="document"
         transport="http://www.openuri.org/2002/04/soap/jms/" /> 
       <! ...    !>
</s0:binding>

<s0:service name="AccountsWebService">
  <s0:port binding="s1:AccountsWebServiceSoapBindingjms"
    name="AccountsJMSPort">
     <s2:address location = 
       "jms://localhost:7020/AccountsWS/  
         AccountsJMS?URI=apps.service.queue&FACTORY=apps.service.cf" /> 
� </s0:port>
</s0:service>
The endpoint address in the sample above comprises the following parts:

Protocol............... jms
Server address......... localhost:7020
Context path........... AccountsWS
Service URI............ AccountsJMS
JMS queue name......... apps.service.queue
JMS connect factory.... apps.service.cf

It's important to note that the WSDL is available on the server using HTTP. For example, the WSDL I'm using is available at http://localhost:7020/AccountsWS/AccountsJMS?WSDL.

WebLogic JAX-RPC Client

A standalone client uses classes generated from the WSDL by the clientgen utility. This utility creates a jar containing the generating JAX-RPC stubs to communicate with the Web service. Here's the Ant file to run clientgen:

<project default="build-client">

  <property name="weblogic.lib" 
    value="D:\bin\bea1020\wlserver_10.0\server\lib" />

  <property name="clientgen.wsdl" 
    value="AccountsWebService.wsdl" />

  <property name="clientgen.jar" value="AccountsWSClient.jar" />

  <property name="clientgen.package" 
    value="com.accounts.wsclient" />


  <path id="weblogic.class.path">
    <filelist dir="${weblogic.lib}">
                <file name="weblogic.jar"/>
        </filelist>
  </path>


  <taskdef name="clientgen"
    classname="weblogic.wsee.tools.anttasks.ClientGenTask" 
                   classpathref="weblogic.class.path"/>  



  <target name="build-client">

    <clientgen
      wsdl="${clientgen.wsdl}"
      destFile="${clientgen.jar}"
      packageName="${clientgen.package}"
        classpathref="weblogic.class.path"/>

  </target>

</project>
The Java Web service client code uses the generated javax.xml.rpc.Service class to get the generated Stub. Just as with the HTTP web service protocol, the stub class has methods to proxy each web service operation. In the example web service implementation above, the operation method name is create and it takes an AccountNameDocument XML object as its only parameter. The returned object is an Account XML object.

Because JMS is the transport protocol the address of the server address can be defined; this determines where the JNDI queue and connection factory objects are retrieved. In addition, a timeout value specified in milliseconds can be set; this determines how long to wait for the Web service response. If left unset, the default behavior is to wait forever.

The Java Web service client is shown below.

package com.accounts.jmswsclient;

import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.Stub;
import weblogic.wsee.jaxrpc.WLStub;
import weblogic.wsee.jaxrpc.WlsProperties;
import com.accounts.accountsxmlschema.AccountType;

public class AccountsWSClient {

  public static void main(String[] args)  {
                
        try {
          AccountsWebServiceJMSService service = 
              new AccountsWebServiceJMSService_Impl();
                        
          AccountsPortType wlstub =  
              service.getAccountsProxyWebServiceJMSport();
                             
        ((Stub)wlstub)._setProperty( 
              WLStub.JMS_TRANSPORT_JNDI_URL, 
              "t3://localhost:7020");
                           
        ((Stub)wlstub)._setProperty(
              WlsProperties.READ_TIMEOUT, 5000);
                        
           AccountType account = wlstub.create("new-account-01");
                        
                        
        } catch (RemoteException e) {
                e.printStackTrace();
        }
        catch (ServiceException e) {
                e.printStackTrace();
        }

  }

}
Other than the two calls to Stub.setProperty(), the Web service client code for the JMS transport is the same as that for the HTTP transport. An XML message is sent and a response is received.

Clients aren't required to use the generated Web service stub built by the clientgen utility. Rather, they can send an XML message to the queue directly using JMS. Note that the XML must be wrapped in a SOAP envelope. In addition, two WebLogic-specific properties must be set on the JMS message, both are string values.

JMS Property Value
_wls_mimehdrContent_Type text/xml; charset="utf-8"
URI /{context-path}/{service-uri}

For our example Web service, the URI value is /AccountsProxyWS/AccountsJMS. This client is also responsible for creating the queue for the response message and setting the JMSReplyTo property.

Configuring the Business Service

The business service on the Oracle Service Bus is created from the WSDL. In our example Web service, I defined bindings for HTTP and JMS; if you're using this as an example, make sure to use the JMS binding or port.

The service endpoint is copied from the WSDL but on the service bus this endpoint is not valid and must be corrected. On the bus a JMS end point is in the form jms://{server:port}/{connection-factory-name}/{queue-name}. The values for the context path and service URI are set as a transport header when the business service is invoked.

If a response is required from the service, a physical response queue is required. Unlike the Web service client using the clientgen generated stub, the bus doesn't create a temporary queue for the reply destination. For the Response Correlation Pattern, I use the JMS Message Id. The screenshot below shows the Business Service Configuration details.

Note that you aren't able to test this service successfully. This is because the URI, which defines the actual Web service to invoke, isn't set. Nor is the other required WebLogic property, the _wls_mimehdrContent_Type. These are set in a proxy service (see configuration details below) that invokes this business service.

Configuring the Proxy Service

A simple proxy service to expose our business service is built from the same WSDL, and like the business service, the end point address needs to be corrected. A different JMS queue than the one used by the business service is needed as well. Unlike the business service, a response queue does not get specified because the proxy's client specifies the response queue.

The proxy needs a pipeline that routes to the business service and set the transport headers as request actions:

The response cannot be consumed by the client unless the response transport header is set with the response mime type.

Updating the Client to use the Proxy Service

Once the proxy has been configured, our client needs to be updated to use the new queue. The queue and connection factory are specified in the endpoint address in the WSDL. This value can be overridden using the JmsTransportInfo class, which allows a new endpoint address to be defined in the client application by setting it as a new property on the Stub. Here is the Java code to set the JmsTransportInfo property:

String uri = "jms://localhost:7020" + 
   "/AccountsProxyWS/AccountsJMS?URI=sb.service.queue";

((Stub)wlstub)._setProperty("weblogic.wsee.connection.transportinfo", 
  new weblogic.wsee.connection.transport.jms.JmsTransportInfo(uri));

Conclusion

Using the SOAP over JMS transport protocol differs from the more common HTTP protocol. The important items to remember are:

  • The endpoint address includes the queue name and connection factory.
  • The URI and mime type need to be set in the service bus' proxy.
  • There are defaults for the read timeout, queue and connection factory. You most likely want to override these values.

Additional Information


Bob Murphy is a Principal Sales Consultant with Oracle. He works in the North America Technology Organization's SOA/BPM Technical Services Group.