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.
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.
The sending servlet will receive inputs from two different sources:
An HTTP GET request from a browser that we’ll use to initiate the sending of a message.
An HTTP POST request containing received SOAP messages, sent by the web service and delivered by the local JAXM provider.
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
):
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");
Tip
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 |
---|---|
Returns a | |
Returns a |
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.
Tip
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.
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:[41]
Construct a SOAP message.
Send it to the web service.
Wait for the reply message.
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).
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.
Warning
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.
Tip
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:
It does not return a value. This, of course, is because
send( )
just sends the message without waiting for a response.It doesn’t provide an argument that specifies the destination address of the message. The destination is assumed either to be part of the message or to be implicitly known to the provider, depending on the profile in use. We’ll see later in Section 4.4 exactly how the addressing works in the case of the SOAP-RP profile.
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.[42]
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:
// Get the message factory msgFactory = conn.createMessageFactory("soaprp"); // Install the factory to use when receiving messages setMessageFactory(msgFactory);
Warning
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:
To receive a SOAP message and return another one that will be sent back to the originator of the message that was received. In this mode, if the message was delivered using an HTTP POST request (which will always be the case in the reference implementation), the reply is sent back in the HTTP response. When used in this way,
JAXMServlet
behaves in the same manner asSAAJServlet
, which is used in Chapter 3.To receive a SOAP message without returning a reply in the HTTP response. This mode is appropriate for asynchronous messaging, and is the only one that can be used in conjunction with a messaging provider (discussed in Section 4.3.3, later in this chapter). If a reply message is subsequently generated, it must be sent via the messaging provider in the usual way.
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.
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);
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:
The sender delivers its outgoing message to its local provider.
The provider puts the message in its outgoing queue.
Some time later, the local provider delivers the message to the receiver’s provider.
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.
[41] 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.
[42] 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 Java Web Services in a Nutshell now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.