Previous section   Next section

4.3 An Example JAXM Application

In order to demonstrate the JAXM APIs and show you how to set up the JAXM provider configuration, we'll look at the implementation of a very simple web service that simply accepts a message and returns it to its sender, having added an XML element containing the date and time at which the message was processed. The process of returning the message requires the receiver to explicitly address the message to its originator and send it back via its own local provider. In other words, each time this service is used, two asynchronous messages are sent, one in each direction.

Since this is a JAXM example, both the sender and the receiver are implemented as servlets in a web container. Both of them need to be able to receive SOAP messages and dispatch them to application code. In Chapter 3, we used a private class called SAAJServlet as the base class for our example web service, but in this chapter, we are going to use the class javax.xml.messaging.JAXMServlet instead. These two servlets are virtually identical � the only real difference between them is that JAXMServlet is included as part of the JAXM API and should therefore be available in all JAXM implementations, whereas SAAJServlet is a private class developed from example code in the JWSDP distribution. We could, of course, have used JAXMServlet in Chapter 3 instead of creating SAAJServlet, but to do so would have introduced a dependency on JAXM, which we wanted to avoid. Like SAAJServlet, JAXMServlet delivers received SOAP messages via the onMessage( ) method, where the application will implement its message handling.

In previous examples, in which the message sender was a freestanding application, we could run it by simply starting the application from the command line. When the message sender is implemented as a servlet, however, we have to take a different approach. We'll implement the servlet's doGet( ) method so that it sends a message to the receiver whenever it is invoked. This allows us to run the example by pointing a web browser at the appropriate URL; this also gives us somewhere to display the message that the receiver returns to us. We don't have a similar issue with the message receiver, which is also deployed as a servlet derived from JAXMServlet, since its task is only to respond to messages when they are delivered to it.

Before we look at the example code, we'll deploy both the sending and receiving servlets. To do this, make sure that the Tomcat web server is running and open a command window. To deploy the receiver, change your current directory to chapter4\soaprpecho relative to the installation directory of the example code for this book and type the command:

ant deploy

This command compiles the code for the receiver and deploys it on the web server. Next, to compile and deploy the sender, change your working directory to chapter4\soaprpsender and type the following command to complete the process:

ant deploy

Although most of the examples in this book need only to be compiled and deployed in order for you to use them, this is not the case here. This example won't work until you have taken some extra steps to configure the JAXM provider, which is covered in Section 4.4, later in this chapter.

4.3.1 Implementing the Sending Servlet for the JAXM Echo Service

Now let's look at how the servlet that provides the functionality of the message sender is implemented. The code for this servlet is shown in Example 4-1.

Example 4-1. The JAXM client for the echo web service
package ora.jwsnut.chapter4.soaprpsender;

import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.messaging.Endpoint;
import javax.xml.messaging.JAXMServlet;
import javax.xml.messaging.OnewayListener;
import javax.xml.messaging.ProviderConnection;
import javax.xml.messaging.ProviderConnectionFactory;
import javax.xml.messaging.ProviderMetaData;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPMessage;
import com.sun.xml.messaging.jaxm.soaprp.SOAPRPMessageFactoryImpl;
import com.sun.xml.messaging.jaxm.soaprp.SOAPRPMessageImpl;

/**
 * A servlet that creates a SOAP-RP message on demand and sends
 * it to a remote echo service.
 */
public class SOAPRPSenderServlet extends JAXMServlet implements OnewayListener {
    
    /**
     * Message returned by the echo service.
     */
    private SOAPMessage replyMessage;
    
    /**
     * Factory used to create parts of SOAP messages
     */
    private SOAPFactory soapFactory;
    
    /**
     * ProviderConnection used to send reply messages
     */
    private ProviderConnection conn;
    
    /**
     * Factory used to create messages
     */
    private MessageFactory msgFactory;
    
    /**
     * Initialize by installing the appropriate MessageFactory
     */
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);
        try {
            // Create the connection to the provider
            conn = ProviderConnectionFactory.newInstance(  ).createConnection(  );
            soapFactory = SOAPFactory.newInstance(  );            
           
            // Check that the soaprp profile is supported
            ProviderMetaData metaData = conn.getMetaData(  );
            String[] profiles = metaData.getSupportedProfiles(  );
            boolean found = false;
            for (int i = 0; i < profiles.length; i++) {
                if (profiles[i].equals("soaprp")) {
                    found = true;
                    break;
                }
            }
            
            if (!found) {
                // No SOAPRP profile
                log("soaprp profile not supported");
                throw new ServletException("soaprp profile not supported");
            }
             
            // Get the message factory and build the message
            msgFactory = conn.createMessageFactory("soaprp");
            
            // Install the factory to use when receiving messages
            setMessageFactory(msgFactory);
        }catch (Exception e) {
                e.printStackTrace(  );
                throw new ServletException(
                     "Failed to initialize SOAPRP sender servlet " + 
                     e.getMessage(  ));
        }
    }
    
    /**
     * Handles a request from a client to send a message.
     */
    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
                                        throws IOException, ServletException {
                                            
        // Only allow gets on the "request" handler
        String path = req.getServletPath(  );
        if (!path.equals("/request")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
                           "Cannot use get on this URL");
            return;
        }
        
        // Build and send a message
        boolean sent = sendMessage(  );
        
        // Wait until the echo service has replied,
        // for a maximum of 30 seconds
        if (sent) {
            synchronized (this) {
                replyMessage = null;
                try {
                    if (replyMessage == null) {
                        wait(30000L);                
                    }
                } catch (InterruptedException ex) {
                }
            }
        }
        
        // Now send the reply to the caller.
        try {
            if (replyMessage == null) {
                resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
                    "No reply received");
                return;
            }

            OutputStream os = resp.getOutputStream(  );
            resp.setContentType("text/html");
            resp.setStatus(HttpServletResponse.SC_OK);
            os.write("<html><P><XMP>".getBytes(  ));
            replyMessage.writeTo(os);
            os.write("</XMP></html>".getBytes(  ));
            os.flush(  );
        } catch (Exception ex) {
            log("Exception in doGet", ex);
            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, 
                          "Exception: " + ex.getMessage(  ));
        }
        
        replyMessage = null; 
    }
    
    /**
     * Handles a POST either from a client or as a 
     * callback from the provider.
     */
    public void doPost(HttpServletRequest req, HttpServletResponse resp) 
                                        throws IOException, ServletException {
        // Only allow posts to the "message" handler
        String path = req.getServletPath(  );
        if (path.equals("/message")) {
            // This is allowed
            super.doPost(req, resp);
        } else {
            // Cannot post to the request path
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
                 "Cannot post to this URL");
        }
    }
    
    /* -- Useful functionality starts here -- */
    /**
     * Builds a message and sends it to the service
     */
    private boolean sendMessage(  ) {
        try {            
            // Build the SOAP-RP message 
            SOAPRPMessageImpl message = 
                (SOAPRPMessageImpl)msgFactory.createMessage(  );
            message.setTo(new Endpoint("urn:SOAPRPEcho"));
            message.setFrom(new Endpoint("urn:SOAPRPSender"));
            SOAPElement element = 
                message.getSOAPPart().getEnvelope(  ).getBody(  ).addBodyElement(
                soapFactory.createName("Sent", "tns", "urn:SOAPRPSender"));
            element.addTextNode("This is the content");
            
            // Send the message to the echo service
            conn.send(message);
            
            // Return indicating that the message was sent.
            return true;
        } catch (Exception ex) {
            log("Failed when sending message", ex);
        }
        
        return false;
    }
    
    /**
     * Handles a received SOAP message - this is the
     * asynchronous reply from the echo service.
     */
    public void onMessage(SOAPMessage message) {
        try {
            synchronized (this) {
                // Save the message for the benefit
                // of the client.
                replyMessage = message;

                // Wake up the client
                notify(  );
            }            
        } catch (Exception ex) {
            log("Exception", ex);
        }
    }    
        
    public void destroy(  ) {
        try {
            if (conn != null) {
                conn.close(  );
            }
        } catch (Exception ex) {
            // Don't log this - the provider may already have closed
        }       
    }
}

Since there is rather a lot of code here, we'll break it down into smaller pieces and examine each of them in turn.

4.3.1.1 Servlet configuration

The sending servlet will receive inputs from two different sources:

We can easily distinguish these two different cases because the former will be handled by the doGet( ) method and the latter by doPost( ), but in order to make the distinction between these two different aspects of the servlet clearer, we choose to assign them to different URLs in the web.xml file that is shown in Example 4-2.

Example 4-2. The web.xml file for the echo example sending servlet
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

    
<web-app>
    <display-name>SOAP-RP Message Sender</display-name>
    <description>SOAP-RP Message Sender</description>
    
    <servlet>
        <servlet-name>SOAPRPSender</servlet-name>
        <display-name>Servlet for the SOAP-RP Message Sender Example
        </display-name>
        <servlet-class>ora.jwsnut.chapter4.soaprpsender.SOAPRPSenderServlet
        </servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>SOAPRPSender</servlet-name>
        <url-pattern>/request</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>SOAPRPSender</servlet-name>
        <url-pattern>/message</url-pattern>
    </servlet-mapping>

</web-app>

The servlet will be deployed with the context path SOAPRPSender, so the URLs that correspond to it are as follows (assuming deployment on localhost):

URL

Description

http://localhost:8080/SOAPRPSender/request

Used by a web browser to initiate the sending of a message

http://localhost:8080/SOAPRPSender/message

Used by the JAXM provider to deliver SOAP messages addressed to the sender

4.3.1.2 Accessing the provider and creating the MessageFactory

When we used SAAJ to send a message, we had to use a factory to create a SOAPConnection, get a MessageFactory to create an empty message, and populate it. We then used the SOAPConnection send( ) method to transmit it and wait for the reply. Here, for example, is the code that we used in the last chapter to obtain SOAPConnection and MessageFactory objects:

// Get a SOAPConnection        
SOAPConnectionFactory connFactory = SOAPConnectionFactory.newInstance(  );
SOAPConnection conn = connFactory.createConnection(  );
MessageFactory messageFactory = MessageFactory.newInstance(  );

When using a messaging provider, the code is almost identical:

ProviderConnectionFactory factory = ProviderConnectionFactory.newInstance(  );
ProviderConnection conn = factory.createConnection(  );
MessageFactory messageFactory = conn.createMessageFactory("soaprp");

The most obvious difference between these two code extracts is that we now use javax.xml.messaging.ProviderConnectionFactory and javax.xml.messaging.ProviderConnection instead of SOAPConnectionFactory and SOAPConnection. These methods (at least notionally) create a connection to the local provider and then use that connection to obtain a MessageFactory for messages to be sent using that provider. Simple and logical though this code might appear, it raises an obvious question: how does the createConnection( ) method know where to find the provider that it is creating a connection to? If you refer back to Figure 4-4, you'll see that the provider is a separate entity. In fact, in the JAXM reference implementation, it is a separate web application that may (or may not) reside on the same host as the JAXM client itself. Yet the JAXM client does not need to provide the URL of the provider in order to connect to it. This is because the URL of the provider is part of the configuration information required to deploy a JAXM client, which we'll look at in Section 4.4, later in this chapter. The fact that you don't need to hardwire it or write code to obtain the URL at runtime simplifies the task of writing the client, and also enhances its portability between different JAXM implementations, since the way in which providers are configured is not covered by the JAXM specification, and is therefore a matter for JAXM vendors that can be addressed at deployment time without the need to modify code.

In the future, should JAXM be more tightly integrated into web or J2EE containers, the ProviderConnectionFactory will probably become a managed object that is configured into the JNDI naming context of the servlet (or message-driven bean) hosting the JAXM client, which would then follow the usual coding pattern to get a reference to it:

InitialContext ctx = new InitialContext(  );
ProviderConnectionFactory factory = (ProviderConnectionFactory)ctx.lookup("key");

In the reference implementation, the createConnection( ) method does not actually cause a connection to be made to the provider, despite its name. A real connection is not established until an attempt is made to send a message, or until the getMetaData( ) method is called.

Another difference between the code shown here and that used in the previous chapter is the way in which the MessageFactory is obtained. When you use a provider, you have to get the MessageFactory using the following ProviderConnection method:

public MessageFactory createMessageFactory(String profile) 
       throws JAXMException;

where profile is a string that represents the message profile to be used when constructing SOAP messages. The reference implementation recognizes two values for this argument, which are described in Table 4-1.

Table 4-1. Messaging profiles supported by the JAXM reference implementation

Profile string

Description

soaprp

Returns a MessageFactory that can be used to create messages with preconfigured headers as defined by the SOAP WS-Routing protocol. WS-Routing (which stands for Web Services Routing) was formerly known as the SOAP Routing Protocol, or SOAP-RP (hence the name of the profile � we'll refer to it throughout this chapter as SOAP-RP or soaprp for consistency with the implementation). It allows the route by which a SOAP message reaches its ultimate destination to be recorded in the message header, and for a suitable reverse path to be created as the message is forwarded through intermediary systems. Section 4.5, later in this chapter, looks at the support for this profile in the JAXM reference implementation.

ebxml

Returns a MessageFactory for messages with headers that conform to the ebXML Message Service specification. See Section 4.6, later in this chapter, for further information on this profile.

JAXM clients that use a messaging provider must use one of the profiles that it supports � it is not possible to create a plain SOAP message using the default MessageFactory (returned by its static newInstance( ) method) and then try to transmit this via a provider.

The consequence of this restriction is that you cannot get the benefits of using a messaging provider unless you use a messaging profile. You can, on the other hand, create a profiled message and use the SOAPConnection call( ) method to send it without using a provider � in fact, you have to do this if you want to use synchronous messaging with a profiled message.

In practice, this is not really a constraint, since applications will typically be written specifically for a particular messaging profile. As you'll see, the code for this example needs to know that it is using a SOAP-RP message and would not work at all with the ebXML profile. For this reason, most clients will obtain the MessageFactory for their profile by directly requesting it by name, as shown earlier:

MessageFactory messageFactory = conn.createMessageFactory("soaprp");

If the provider does not support the requested profile, this method throws a JAXMException. You can discover the set of profiles that a messaging provider supports by obtaining its ProviderMetaData object using the following ProviderConnection method:

public ProviderMetaData  getMetaData(  ) throws JAXMException;

and then calling the getSupportedProfiles( ) method, which returns the profile names in the form of a string array. Here's how the example source code uses this method to check whether the SOAP-RP profile is supported, as an alternative to catching a JAXMException from the createMessageFactory( ) method:

// Check that the soaprp profile is supported
ProviderMetaData metaData = conn.getMetaData(  );
String[] profiles = metaData.getSupportedProfiles(  );
boolean found = false;
for (int i = 0; i < profiles.length; i++) {
    if (profiles[i].equals("soaprp")) {
        found = true;
        break;
    }
}

if (!found) {
    // No SOAPRP profile
    log("soaprp profile not supported");
     throw new ServletException("soaprp profile not supported");
}

If you refer to Example 4-1, you'll notice that the code that handles the connection to the messaging provider and the obtaining of a MessageFactory is all executed in the servlet's init( ) method. This is appropriate because a single instance of all of these objects can be shared among all users of the servlet; therefore, they need only to be created once, when the servlet is first loaded.

4.3.1.3 Handling requests from the browser

Having initialized the servlet by connecting to the provider and getting a MessageFactory, there is nothing further to do until the user visits its request URL (http://localhost:8080/SOAPRPSender/request), at which time the servlet's doGet( ) method is called. At this point, we want to do the following:[2]

[2] Note that the doGet( ) method would also be called if the browser were incorrectly given the URL http://localhost:8080/SOAPRPSender/message, which is intended to be used for delivery of the response message from the web service. In order to exclude this case, the doGet( ) method uses the HttpServletRequest getServletPath( ) method to determine the URL used to call it, and returns an error if appropriate.

  1. Construct a SOAP message.

  2. Send it to the web service.

  3. Wait for the reply message.

  4. Send an HTTP response to the browser containing a copy of the SOAP message returned by the web service.

We'll cover the details of the construction of the outgoing message and the handling of the reply later. The important point to deal with here is that the response to the browser has to be sent before the doGet( ) method completes, which means that doGet( ) has to receive and process the web service's reply.

The problem with this is that when we use a provider, the method that sends a message does not block until the reply arrives � it returns control as soon as the message is handed to the provider. The reply message arrives at some time in the future � or perhaps not at all. Hence, the doGet( ) method needs to arrange to block until it is notified that the web service's reply message has been received. We achieve this by using an instance variable that is initially set to null, but which is used to store a reference to the reply message when it arrives. The doGet( ) method uses the Object wait( ) method to pause for up to 30 seconds. When it resumes, it is either because the reply was received (in which case it prepares a reply containing the message content) or because the time limit expired (when it sends a response containing an error status).

4.3.1.4 Constructing and sending the message

The message that we're going to send to the web service is obtained using the createMessage( ) method of the SOAP-RP profile's MessageFactory:

// Build the SOAP-RP message 
SOAPRPMessageImpl message = (SOAPRPMessageImpl)msgFactory.createMessage(  );
message.setTo(new Endpoint("urn:SOAPRPEcho"));
message.setFrom(new Endpoint("urn:SOAPRPSender"));
SOAPElement element = message.getSOAPPart().getEnvelope(  ).getBody( ).
addBodyElement(

soapFactory.createName("Sent", "tns", "urn:SOAPRPSender"));
element.addTextNode("This is the content");

The first thing to note about this code is that the object returned from the createMessage( ) method � although it is a SOAPMessage � is cast to a reference of type SOAPRPMessageImpl. The SOAPRPMessageImpl class, which resides in the com.sun.xml.messaging.jaxm.soaprp package, is the reference implementation's representation of a SOAP-RP message, and provides convenience methods that allow you to set and retrieve the content of XML elements that form the header entry that SOAP-RP defines without needing to understand the way in which the element structure is built. This class, which is not covered by the JAXM specification, is described in more detail in Section 4.5, later in this chapter.

Both of the profiles supported by the reference implementation use message classes that are defined in packages that are not covered by the JAXM specification. Unfortunately, due to the fact that you have to use a profiled message with a message provider, you have no choice but to use these classes despite the fact that their API is not formally defined, and hence might be subject to change in the future.

SOAP-RP defines two particular header fields that hold the source and ultimate destination addresses of the message. Both are defined as URIs. The SOAPRPMessageImpl class allows you to set these fields using the setFrom( ) and setTo( ) methods, both of which accept an argument of type javax.xml.messaging.Endpoint. Endpoint is the most general form of address recognized by JAXM. It simply encapsulates a string, the interpretation of which depends entirely on the profile used to create the message and the messaging provider. In this case, the to and from addresses, which are urn:SOAPRPEcho and urn:SOAPRPSender respectively, are URNs rather than URLs. As we'll see later in Section 4.4, these values are simply tokens that are mapped to URLs by the messaging provider.

Using tokens instead of absolute addresses has the obvious advantage that the code is completely independent of the actual addresses that are eventually used. The only requirement is that the sending provider has a mapping for the token that represents the destination address, and the receiving provider has a mapping for the sender's token if it is necessary to send a reply. It is, of course, possible to use the real URLs as the tokens, but it is still necessary to create a trivial identity mapping in the provider configuration.

Endpoint has a subclass called URLEndpoint that explicitly indicates that the address is supplied in the form of a string that represents a URL. This class does not really add anything more than cosmetic value, because there is no validity checking applied to the address, and it is not possible to use it in conjunction with a real java.net.URL object. It is really just a convenience for SAAJ applications that need to deal directly with URLs (because they do not have a messaging provider to map from a URI), and is largely redundant as of SAAJ 1.1 because the SOAPConnection send( ) method accepts a string argument such as http://localhost:8080/BookImageService as well as the more complex new URLEndpoint("http://localhost:8080/BookImageService"). Note, however, that you cannot simply pass a string to the setTo( ) and setFrom( ) methods of SOAPRPMessageImpl, and that all address handling inside the messaging provider uses Endpoint objects.

Having addressed the message and added a simple XML node containing some text, the last step is to transmit it, which is achieved using the ProviderConnection send( ) method:

public void send(SOAPMessage message) throws JAXMException;

Note the two very important differences between this method and the SOAPConnection call( ) method used in Chapter 3:

4.3.1.5 Handling the response message

At some point after the message is transmitted, the receiving servlet (the code for which will be shown shortly) returns a modified version of it to our local messaging provider, which then uses an HTTP POST request to deliver it to our sending servlet's doPost( ) method.[3] Since SOAPRPSenderServlet is derived from JAXMServlet, its doPost( ) method decodes the HTTP POST request and converts its content into a SOAPMessage, using code that is very much like that shown in Example 3-1 in Chapter 3. The SOAPMessage is then delivered to our onMessage( ) method. In order to decode the content of the HTTP request, JAXMServlet needs an appropriate MessageFactory, so that it can call its createMessage( ) method in the same way as SAAJServlet does in Example 3-1. For a SAAJ application, the default MessageFactory can be used, but when using a messaging provider, it is necessary to install the factory for the messaging profile in use by using the JAXMServlet setMessageFactory( ) method. This is typically done in the servlet's init( ) method, once the ProviderConnection and MessageFactory have been obtained:

[3] You are probably wondering exactly how the messages manage to find their way from the sending servlet to the receiver and back again when all that we have supplied is a pair of seemingly meaningless URNs. We'll show exactly how this works in Section 4.4, later in this chapter. For now, we're looking only at how the messages themselves are created and handled, which is the only thing that affects the code that you actually write. I am deliberately remaining silent on configuration, which is a deployment issue, to avoid presenting too complex a picture.

// Get the message factory
msgFactory = conn.createMessageFactory("soaprp");

// Install the factory to use when receiving messages
setMessageFactory(msgFactory);

If you don't install a MessageFactory in the servlet's init( ) method, JAXMServlet uses the default MessageFactory to decode the messages that it receives. This won't cause any problems with the process of creating the internalized form of the message, but all the messages will be of type SOAPMessage and not the profile-specific subclass that you are probably expecting, the likely result of which is a ClassCastException.

In most cases, then, if you use JAXMServlet as the base class for your JAXM client, you need only to override onMessage( ) to be able to handle messages directed at your client, and there is no need to override doPost( ) yourself. In this example, however, since our servlet is mapped to two URLs, we would like to ensure that only POST requests sent to the URL that ends with /SOAPRPSender/message are treated as SOAP messages. To do this, we override doPost( ) to inspect the URL to which the POST was directed, and invoke the JAXMServlet doPost( ) method only if the correct URL was used:

public void doPost(HttpServletRequest req, HttpServletResponse resp) 
                throws IOException, ServletException {
    // Only allow posts to the "message" handler
    String path = req.getServletPath(  );
    if (path.equals("/message")) {
        // This is allowed
        super.doPost(req, resp);
    } else {
        // Cannot post to the request path
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, 
             "Cannot post to this URL");
    }
}

Once the message is handed to the onMessage( ) method, all we need to do is store a reference to it in the replyMessage instance variable and use the notify( ) method to wake up the thread that handled the original request from the browser that is blocked in the doGet( ) method.

When you derive your servlet class from JAXMServlet, you have to provide an onMessage( ) method to receive SOAP messages. There are two different ways to use the JAXMServlet onMessage( ) method:

JAXMServlet distinguishes these two cases based on which of two interfaces your servlet subclass implements. If it implements the javax.xml.messaging.ReqRespListener interface, the onMessage( ) method must have the following signature:

public SOAPMessage onMessage(SOAPMessage message);

This interface should only be used as an alternative to using SAAJServlet. Servlets that work with a messaging provider (including the ones shown in this chapter) must implement the javax.xml.messaging.OnewayListener interface, in which the onMessage( ) method does not return a SOAPMessage:

public void onMessage(SOAPMessage message);

If your servlet does not declare that it implements one or the other of these interfaces, then the doPost( ) method throws a ServletException when it receives a SOAP message.

4.3.2 Implementing the Receiving Servlet for the JAXM Echo Service

The servlet that implements the web service itself and that receives the messages sent by SOAPRPSenderServlet is also derived from JAXMServlet and implements the OnewayListener interface. Unlike SOAPRPSenderServlet, however, it has only one source of message (its local messaging provider). Therefore, it needs only to be mapped to a single URL in its web.xml file, which is shown in Example 4-3.

Example 4-3. The web.xml file for the echo example receiving servlet
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

    
<web-app>
    <display-name>SOAP-RP Echo Service</display-name>
    <description>SOAP-RP Echo Service</description>
    
    <servlet>
        <servlet-name>SOAPRPEcho</servlet-name>
        <display-name>Servlet for the SOAP-RP Message Echo Service</display-name>
        <servlet-class>ora.jwsnut.chapter4.soaprpecho.SOAPRPEchoServlet
        </servlet-class>
        <load-on-startup>1000</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>SOAPRPEcho</servlet-name>
        <url-pattern>/message</url-pattern>
    </servlet-mapping>
</web-app>

Given that this servlet is deployed with the context path SOAPRPEcho, the URL used to deliver SOAP messages to it would be http://localhost:8080/SOAPRPEcho/message.

An important point to note about this web.xml file is that it contains the following line:

<load-on-startup>1000</load-on-startup>

This causes the servlet to be initialized when it is deployed and when the web container starts up, without waiting for it to be invoked as a result of an HTTP request. This is essential for this servlet, but the reason is impossible to describe until we discuss how the provider communicates with the servlet in Section 4.4, later in this chapter. It is not necessary to load the sending servlet at startup, because it is not required until it is asked to send a message as a result of a browser making an HTTP GET request to its /request URL.

Like the sending servlet, SOAPRPEchoServlet overrides the JAXMServlet init( ) method to obtain a connection to the messaging provider and install a MessageFactory. The code is almost identical to that shown earlier, but there are some interesting differences. The implementation is shown in Example 4-4.

Example 4-4. The init( ) method of the receiving servlet
public void init(ServletConfig servletConfig) throws ServletException {
    super.init(servletConfig);

    // Workaround for hang when deploying in J2EE container.
    // Do not connect to the provider here - defer to another thread.
    new Thread(new Runnable(  ) { 
        public void run(  ) {
            try {
                // Create the connection to the provider
                conn = ProviderConnectionFactory.newInstance(  )
                   .createConnection(  );

                // Work around for a JAXM bug
                conn.getMetaData(  );

                // Install the message factory for the SOAP-RP profile
                setMessageFactory(conn.createMessageFactory("soaprp"));
            } catch (Exception e) {
                log("Exception when initializing", e);
            }
       }
    }).start(  );
}

The ProviderConnection getMetaData( ) method is called for no apparent reason � the value that it returns is ignored. In JWSDP Version 1.1, this is a workaround for a problem that we'll explain later in Section 4.4. If you don't do this, the provider will be unable to deliver messages to the servlet.

The onMessage( ) method handles each message that it receives by creating a copy in which the to and from addresses are reversed, and adding an element that contains the date and time at which the message was processed. The new message is then sent to the provider, which delivers it to the original sender (or, more precisely, whatever the from address points to). An extract from this method showing the most interesting lines of code is shown in Example 4-5.

Example 4-5. Handling a SOAP message and returning an asynchronous reply
public void onMessage(SOAPMessage message) {

    try {

        // Create a copy of the message with the same body
        // and with the to and from addresses exchanged.
        SOAPRPMessageImpl soapMsg = (SOAPRPMessageImpl)message;
        MessageFactory factory = soapMsg.getMessageFactory(  );

        // Create a reply message
        SOAPRPMessageImpl replyMsg = (SOAPRPMessageImpl)factory.createMessage(  );

        // Move the body content from the received message to the source.
        SOAPBody body = soapMsg.getSOAPPart().getEnvelope(  ).getBody(  );
        SOAPBody replyBody = replyMsg.getSOAPPart().getEnvelope(  ).getBody(  );
        Iterator iter = body.getChildElements(  );
        while (iter.hasNext(  )) {
            SOAPElement element = (SOAPElement)iter.next(  );
            replyBody.addChildElement(element);
        }

        // Add an element after the body that contains the date.
        SOAPElement element = 
                replyMsg.getSOAPPart(  ).getEnvelope(  ).addChildElement(
                     "Date", "tns", "urn:SOAPRPEcho");
        element.addTextNode(new Date(  ).toString(  ));

        // Copy any attachments
        iter = soapMsg.getAttachments(  );
        while (iter.hasNext(  )) {
            replyMsg.addAttachmentPart((AttachmentPart)iter.next(  ));
        }

        // Get the SOAP message ID and install it as the "relates-to" value
        replyMsg.setRelatesTo(soapMsg.getSOAPRPMessageId(  ));

        // Get the the "To" address and install it as the "From" address
        replyMsg.setFrom(soapMsg.getTo(  ));

        // Get the "From" address an install it as the "To" address
        replyMsg.setTo(soapMsg.getFrom(  ));

        // [ CODE HERE NOT SHOWN]

        // Send the reply message
        conn.send(replyMsg);
    } catch (Exception ex) {
        log("Exception", ex);
    }
}

First, the received message is cast to the SOAPRPMessageImpl object so that we can use the convenience methods that it provides to get access to the SOAP-RP fields of the header that we need, especially the to and from addresses. This works because we installed the appropriate MessageFactory in the servlet's init( ) method (and, of course, because we are receiving SOAP-RP messages!). Next, we create an empty message and copy the content of the received message's body and its attachments (if there are any) into it. Although we could use the MessageFactory created in the init( ) method to create the copy, we take the opportunity to demonstrate the SOAPRPMessageImpl getMessageFactory( ) method, which returns a reference to the MessageFactory that was used to create the received message, and use the same one to build the reply.

Next, we add an element to the new message that contains the current date and time:

// Add an element after the body that contains the date.
 SOAPElement element = 
     replyMsg.getSOAPPart(  ).getEnvelope(  )
     .addChildElement("Date", "tns", "urn:SOAPRPEcho");
 element.addTextNode(new Date(  ).toString(  ));

You'll notice that we add this element directly to the SOAP envelope, which means that it appears after the body element rather than inside the body. There is no special reason for this, other than to demonstrate that this is allowed both by the SOAP specification and the SAAJ API.

Finally, we swap the to and from addresses using the SOAPRPMessageImpl convenience methods that provide easy access to these fields. Then, we use the ProviderConnection send( ) method to send the copied and modified message back to the original sender:

 // Get the the "To" address and install it as the "From" address
replyMsg.setFrom(soapMsg.getTo(  ));

// Get the "From" address an install it as the "To" address
 replyMsg.setTo(soapMsg.getFrom(  ));
        
// Send the reply message
conn.send(replyMsg);

4.3.3 Why Synchronous Messaging Does Not Work with a Provider

When you use a messaging provider, you have no choice but to send your message asynchronously. Furthermore, in the example that we have been looking at in this section, the message receiver is a subclass of JAXMServlet that implements OnewayListener, and returns its reply using another asynchronous call to its local provider. The obvious question to ask is, since the receiver in this case can process the message and generate its reply immediately, why can't it implement the ReqRespListener interface and return the reply message for the provider to send straight back? Although this might seem simpler (and nothing stops you from trying it), it won't actually work � your message will not be delivered.

To understand why you can't implement a synchronous receiver to respond to an asynchronous message, look at the sequence of events that happens when the original message is transmitted:

  1. The sender delivers its outgoing message to its local provider.

  2. The provider puts the message in its outgoing queue.

  3. Some time later, the local provider delivers the message to the receiver's provider.

  4. Some time later, the receiver's provider delivers the message to the receiving servlet.

The last step in this process is the critical one. The message is delivered to the servlet by the receiver's provider using an HTTP POST request. If the receiving servlet implemented ReqRespListener, it could return a SOAPMessage and JAXMServlet would place it in the HTTP response. However, the receiving provider is simply not expecting this behavior � it does not examine the content of the HTTP response, so the message is lost.


  Previous section   Next section