The SOAP specification does not provide a programming model or even an API for the construction of SOAP messages; it simply defines the XML schema to be used in packaging a SOAP message. The SAAJ specifications and the java.xml.soap package defines objects to construct and deconstruct a SOAP message and send them without the support of a provider. The SAAJ API introduced in Chapter 4 essentially provides a DOM-like tree representation of a SOAP message to developers. Figure 11.28 shows the relationships between the core objects in the javax.xml.soap package and how they model the physical SOAP message.
Let us look at the steps involved on the message sender's side for point-to-point or synchronous XML messaging with JAXM. The steps involved (also shown in Figure 11.29) are remarkably similar to the steps outlined for JMS earlier:
Create a MessageFactory for creating message objects.
Create a Message from the message factory.
Populate the message with the relevant information and content (such as attachments).
Create a SOAPConnectionFactory.
Create the SOAPConnection from the factory.
Create the Endpoint for the connection.
Send the message to the endpoint using the connection and receive the SOAPMessage as the response.
A MessageFactory is a factory to instantiate and create messages. Without a provider, the client uses the default method:
MessageFactory mfactory = MessageFactory.newInstance(); SOAPMessage message = mfactory.createMessage();
The message object created from the factory contains the logical SAAJ DOM representation of the SOAP message. The SOAPMessage message object above contains
A SOAPPart object that contains
A SOAPEnvelope object that contains
An empty SOAPHeader object
An empty SOAPBody object
Once the SOAPMessage has been created, the client can add content to it by accessing the subparts of the envelope:
SOAPPart soapPart = message.getSOAPPart(); SOAPEnvelope envelope = soapPart.getEnvelope(); SOAPHeader header = envelope.getHeader(); SOAPBody body = envelope.getBody(); Name name=envelope.createName("PurchaseOrder", "po","http://www.flutebank.com/xml"); SOAPBodyElement element = body.addBodyElement(name); Name sendername = envelope.createName("senderid"); SOAPElement symbol = element.addChildElement(sendername); symbol.addTextNode("myuserid@"+new Date().toString()) ;
The payload(s) can be attached as necessary. The code below demonstrates how an XML file representing a purchase order is attached to the SOAP message. As Figure 11.29 shows, the AttachmentPart models the MIME attachments in the physical SOAP message.
AttachmentPart ap1 = message.createAttachmentPart(); ap1.setContent(new StreamSource("sample.xml"), "text/xml"); message.addAttachmentPart(ap1);
Once the message is ready and available to be processed, it must be sent. To create a connection factory, the SAAJ API's javax.xml.soap.SOAPConnectionFactory class is used, which has a default implementation:
SOAPConnectionFactory factory = SOAPConnectionFactory.newInstance();
Alternatively, because factories are administered objects (as in JMS), they can be obtained from the JNDI context in the client's runtime, if the vendor supports it. In that case, the code would look something like the following:
Hashtable env = new Hashtable(); //set environment properties in InitialContext ctx = new InitialContext(env); SOAPConnectionFactory factory = (SOAPConnectionFactory) ctx.lookup(SOAPFACTORY);
Once the factory is available, it is used to create the underlying connection. A SOAPConnection represents a point-to-point connection between a sender and a receiver. Note that creating a connection does not imply that a socket has been opened or any underlying network connections have been established, because the destination or endpoint has not been configured until now.
SOAPConnection con = factory.createConnection();
Now that we have created our message, we want to send it, using the connection we made earlier:
SOAPConnection connection = factory.createConnection(); URLEndpoint endpoint = new URLEndpoint (url); SOAPMessage response = connection.call(message, endpoint);
The SOAPMessage returned from the above call can either be traversed using the SAAJ methods or just dumped to a stream for debugging response.writeTo(System.out );
Listing 11.5 shows the SOAP message created and sent by JAXM.
POST /flutejaxmsync/syncjaxm HTTP/1.1 Content-Type: multipart/related; type="text/xml"; boundary="----=_Part_0_3237557.1029815730110" Content-Length: 1229 SOAPAction: "" User-Agent: Java1.3.1_04 Host: 127.0.0.1:9090 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive ------=_Part_0_3237557.1029815730110 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header/> <soap-env:Body> <po:PurchaseOrder xmlns:po="http://www.flutebank.com/schema"> <senderid>myuserid@Mon Aug 19 23:55:28 EDT 2002</senderid> </po:PurchaseOrder> </soap-env:Body> </soap-env:Envelope> ------=_Part_0_3237557.1029815730110 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <purchaseorder xmlns="http://www.flutebank.com/schema" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.flutebank.com/schema" xsi:schemaLocation="http://www.flutebank.com/schema purchaseorder.xsd"> <identifier>87 6784365876JHITRYUE</identifier> <date>29 October 2002</date> <billingaddress> <name>John Malkovich</name> <street>256 Eight Bit Lane</street> <city>Burlington</city> <state>MA</state> <zip>01803-6326</zip> </billingaddress> <items> <item> <quantity>3</quantity> <productnumber>229AXH</productnumber> <description>High speed photocopier machine with automatic sensors </description> <unitcost>1939.99</unitcost> </item> <item> <quantity>1</quantity> <productnumber>1632</productnumber> <description>One box of color toner cartridges</description> <unitcost>43.95</unitcost> </item> </items> </purchaseorder> ------=_Part_0_3237557.1029815730110--
Just as the SOAPMessage was created and populated using the SAAJ API, the response SOAPMessage returned by the service can be parsed and extracted:
SOAPPart sp = response.getSOAPPart(); SOAPEnvelope se = sp.getEnvelope(); SOAPBody sb = se.getBody(); Iterator it = sb.getChildElements(); while (it.hasNext()) { SOAPBodyElement bodyElement = (SOAPBodyElement) it.next(); System.out.println(bodyElement.getElementName().getQualifiedName() + " namepace uri=" +bodyElement.getElementName().getURI()); Iterator it2 = bodyElement.getChildElements(); while (it2.hasNext()) { SOAPElement element2 = (SOAPElement) it2.next(); System.out.print(element2.getElementName().getQualifiedName() +" = "); System.out.println(element2.getValue()); } }
The attachments in the messages returned from the server can also be processed, as shown below. Since the attachment is of text/xml type, it maps to a javax.xml.Source type. Table 11.1 shows the three mappings between MIME types and Java objects defined by the SAAJ specifications, which all implementations must support. Though the mappings are a subset of those defined in the JAX-RPC specifications, they follow the same guidelines and use the JavaBeans Activation framework.
MIME type |
Java type |
---|---|
text/plain |
java.lang.String |
multipart/* |
javax.mail.internet.MimeMultipart |
text/xml or application/xml |
javax.xml.transform.Source |
Iterator attachments = response.getAttachments(); while(attachments.hasNext()){ AttachmentPart part= (AttachmentPart)attachments.next(); System.out.println("Message returned has attachment of type :" + part.getContentType()); System.out.println("Message returned has attachment content : \n" ); // use a null transformer. See Chapter 9 for details Transformer nulltransformer =TransformerFactory.newInstance().newTransformer(); nulltransformer.transform((StreamSource)part.getContent(), new StreamResult(System.out)); }
Once the client has been compiled, it can be executed. Note that unlike with JAX-RPC, there is no concept of stubs, DII, or dynamic proxies in messaging. Conceptually, messaging is all about senders and receivers. Listing 11.6 shows the output from the client.
C:\webservices>java SyncClient cid:CorrelationID namepace uri=http://www.flutebank.com/schema messageid = 9812398ABHCOIUU timestamp = Tue Aug 20 01:46:47 EDT 2002 Message returned has attachment of type :text/xml Message returned has attachment content : <?xml version="1.0" encoding="UTF-8"?> <invoice xmlns="http://www.officemin.com/schema" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.officemin.com/schema" xsi:schemaLocation="http://www.officemin.com/schema invoice.xsd"> <purchaseorder> <identifier>87 6784365876JHITRYUE</identifier> <date>29 October 2002</date> </purchaseorder> <items> <item> <quantity>3</quantity> <productnumber>229AXH</productnumber> <description>High speed photocopier machine with automatic sensors</description> <unitcost>1939.99</unitcost> </item> <item> <quantity>1</quantity> <productnumber>1632</productnumber> <description>One box of color toner cartridges</description> <unitcost>43.95</unitcost> </item> </items> <paymentdetails> <invoicenumber>8912737821ATYWER </invoicenumber> <currency>USD</currency> <total>5864</total> <payableto>officemin.com</payableto> <mailingaddress> 64 Bit True Unix Street, Windsor, CT, USA</mailingaddress> </paymentdetails> </invoice>
We have looked at how to develop a synchronous client that sends a message to a service. Let us now look at how the message receiver can be implemented.
A developer-written service that exhibits synchronous or request-response behavior must implement the javax.xml.messaging.ReqRespListener interface. The service itself can be deployed as a servlet endpoint or EJB endpoint. We will look at EJBs later in the chapter. For the service to be deployed as a servlet endpoint requires the servlet to implement the javax.xml.messaging.ReqRespListener interface, which has one method:
public SOAPMessage onMessage(SOAPMessage message);
The logic to process the request must be plugged into the implementation of this method, because the implementation receives the incoming SOAP message and returns the response to that message. Developers must still write the code that extracts the SOAPMessage from the incoming HTTP request and invoke this method in their servlet implementations. To save time, the JAXM reference implementation comes with a utility in the form of the javax.xml.messaging.JAXMServlet, which contains this logic and can be extended by developers (giving the appearance that onMessage() is invoked automatically). Note that this is not a part of the JAXM specifications. Vendors are not required to provide this utility, and developers are not required to use it.
Figure 11.30 shows the implementation scheme for a synchronous service. It implements the ReqRespListener and optionally extends JAXMServlet. Listing 11.7 shows the code for the service. Ideally, the XML representing the purchase order would be extracted from the SOAPMessage, and business logic applied to it. This example just returns an invoice document without looking at the contents of the request.
package com.flutebank.jaxmservice; // imports not shown for brevity public class PurchaseOrderService extends JAXMServlet implements ReqRespListener { private ServletContext context ; // Servlet init method public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); context=servletConfig.getServletContext(); } /** * SOAP Message containing the purchase order is received, and a SOAP * message containing an invoice is returned * */ public SOAPMessage onMessage (SOAPMessage soapMessage) { // insert some logic here to process the incoming message // with the purchase order here // Then return the invoice try{ // create a message to return MessageFactory mfactory = MessageFactory.newInstance(); SOAPMessage message = mfactory.createMessage(); // get the body of the message created SOAPPart soap = message.getSOAPPart(); SOAPEnvelope envelope = soap.getEnvelope(); SOAPHeader header = envelope.getHeader(); SOAPBody body = envelope.getBody(); // add some elements to the body Name name=envelope.createName("CorrelationID", "cid","http://www.flutebank.com/schema"); SOAPBodyElement element = body.addBodyElement(name); Name messageid = envelope.createName("messageid"); SOAPElement msg = element.addChildElement(messageid); msg.addTextNode("9812398ABHCOIUU") ; Name timestamp = envelope.createName("timestamp"); SOAPElement date = element.addChildElement(timestamp); date.addTextNode(new Date().toString()) ; // add the invoice as an attachment to the message AttachmentPart attach = message.createAttachmentPart( new DataHandler(context.getResource ("/WEB-INF/invoice.xml"))); attach.setContentType("text/xml"); message.addAttachmentPart(attach); message.saveChanges(); // return the newly create SOAP message with the attachment return message; }catch(Exception e){ System.out.println("Exception occured in onMessage() "+e); // return the original message in case of a problem. return soapMessage; } } }
In point-to-point messaging if the client does not receive a response because of a communication error, the server or client cannot recover automatically. The logic for retrying the communication must be built into the client. Request-response services must take this into account, and architects must design a correlation-retry mechanism. This may include schemes such as message initiators sending every request with a unique ID and servers keeping track of the request IDs processed. The PurchaseOrderService example in Listing 11.7 includes some dummy timestamp and ID information in the response it sends back.
Because the service is exposed with a servlet endpoint, it can be deployed in the standard WAR archive format. Listing 11.8 shows the response returned by the service.
HTTP/1.1 200 OK Content-Type: multipart/related; type="text/xml"; boundary="----=_Part_1_2588785.1031512962862" Content-Length: 1636 SOAPAction: "" Date: Sun, 08 Sep 2002 19:22:42 GMT Server: Apache Coyote HTTP/1.1 Connector [1.0] ------=_Part_1_2588785.1031512962862 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header/> <soap-env:Body> <cid:CorrelationID xmlns:cid="http://www.flutebank.com/schema"> <messageid>9812398ABHCOIUU</messageid> <timestamp>Sun Sep 08 15:22:42 EDT 2002</timestamp> </cid:CorrelationID> </soap-env:Body> </soap-env:Envelope> ------=_Part_1_2588785.1031512962862 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <!--edited by Sameer Tyagi (none)--> <invoice xmlns="http://www.officemin.com/schema" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.officemin.com/schema" xsi:schemaLocation="http://www.officemin.com/schema invoice.xsd"> <purchaseorder> <identifier>87 6784365876JHITRYUE</identifier> <date>29 October 2002</date> </purchaseorder> <items> <item> <quantity>3</quantity> <productnumber>229AXH</productnumber> <description>High speed photocopier machine with automatic sensors</description> <unitcost>1939.99</unitcost> </item> <item> <quantity>1</quantity> <productnumber>1632</productnumber> <description>One box of color toner cartridges</description> <unitcost>43.95</unitcost> </item> </items> <paymentdetails> <invoicenumber>8912737821ATYWER </invoicenumber> <currency>USD</currency> <total>5864</total> <payableto>officemin.com</payableto> <mailingaddress> 64 Bit True Unix Street, Windsor, CT, USA</mailingaddress> </paymentdetails> </invoice> ------=_Part_1_2588785.1031512962862--
Earlier in the chapter, we discussed the concept of providers. The client-side provider plays a proxying role and accepts the message on behalf of the intended recipients, providing an asynchronous façade to the client application. To send a message asynchronously, a service client must meet two requirements: it must use a JAXM provider, and it must use a messaging profile with the provider.
If a client does not use a provider to deliver its messages, it is essentially behaving like a synchronous client. As discussed in the MOM topologies, it is not necessary that a provider and its client coexist in the same address space (e.g., process). However, if the provider does not expose itself remotely (e.g., the client API for connecting to the provider is not implemented with the ability to connect to the provider over the network), the client and provider must coexist. This is the case with the reference implementation. The way an application communicates with its provider is vendor-implementation-specific and is not imposed by JAXM.
The core of the JAXM API that relates to asynchronous messaging resides in the javax.xml.messaging package and is shown in Table 11.2.
OnewayListener |
An interface for services intended to be consumers of asynchronous messages |
---|---|
ProviderConnection |
Represents a connection to a provider |
ProviderConnectionFactory |
Factory for creating ProviderConnection objects |
ProviderMetaData |
Provides information about the provider |
ReqRespListener |
An interface for components intended to be consumers of synchronous messages |
The class diagram in Figure 11.31 shows how a JAXM client can send a message asynchronously using a provider, and the steps involved:
Create a connection to the provider.
Use the connection to the provider to create messages.
Populate the message with the relevant information and content (such as attachments).
Send the message to the endpoint, using the connection to the provider.
A connection to the provider is represented by ProviderConnection and is obtained using a ProviderConnectionFactory. Clients can use the default ProviderConnectionFactory instantiation mechanism, which will pick up the default configuration. Alternatively, they can use JNDI for the lookup, using a logical name. For JNDI lookups, the ProviderConnection factory is configured at deployment time with the relevant information to connect to a particular provider and bound to the JNDI tree in the J2EE container with a logical name. Applications look up the tree using the logical name as a key and get back the instance. (JNDI lookups are a standard technique in J2EE but will work only for J2EE containers that support JNDI, which Tomcat and the Java WSDP do not.) This is analogous to JMS-administered objects and the manner in which QueueConnectionFactory and TopicConnectionFactory are located.
ProviderConnectionFactory providerFactory = ProviderConnectionFactory.newInstance(); // alternate technique HashMap properties = new HashMap(); properties.put("some property", "some value"); // set other properties. InitialContext ctx = new InitialContext(properties); ProviderConnectionFactory providerFactory =(ProviderConnectionFactory) ctx.lookup("java:comp/env/jaxm/providerfactory");
The ProviderConnectionFactory is used to create the ProviderConnection, which represents an active connection to the provider.
ProviderConnection fluteprovider = providerFactory.createConnection();
The ProviderConnection is a heavyweight reference, because its instantiation might lead to possible network communications and authentication calls with the provider (depending on the topology used). Therefore, keeping the reference longer than necessary will keep those resources blocked (this is analogous to how a JDBC Connection is set up). Because of this, the connection should be closed as soon it is not needed. Vendors are free to implement pooling and other optimization techniques for this connection.
Once a ProviderConnection has been obtained, it can be used to instantiate one or more MessageFactory objects, which in turn can be used to create SOAPMessage objects.
Messagefactory messageFactory = fluteprovider.createMessageFactory("ebxml");
Earlier in the chapter, we talked about the need for messaging profiles. A messaging profile is required for asynchronous delivery by a provider to some eventual destination, because the provider has to know the messaging semantics to actually deliver the message. These are not inherently defined by SOAP. (Note that a MessageFactory can be instantiated only for a particular profile.) If a profile name is not specified, the provider must default to some internal profile that may be vendor-specific. In such cases, the application may not interoperate with other applications that rely on standard profile information.
Once the factory is available, it can be used to generate empty SOAPMessage objects, which are then populated with the message data.
SOAPMessage message = messageFactory.createMessage();
Typically, the messages created by the factory will either use vendor utility API or be profile-specific vendor-implementation subclasses of SOAPMessage that expose methods to wrap the profile-specific information (such as messaging headers). For example, the reference implementation contains minimal profiles for ebXML messaging ("ebxml") and Web Services Routing Protocol ("soaprp"). The message class corresponding to these profiles are com.sun.xml.messaging.jaxm.ebxml.EbXMLMessageImpl and com.sun.xml.messaging.jaxm.soaprp.SOAP-RPMessageImpl respectively.
EbXMLMessageImpl message = (EbXMLMessageImpl)messageFactory.createMessage();
This can now be populated with the ebXML-specific headers, using convenient methods to construct relevant XML tags:
// set send and receive provider message.setSender(new Party(from)); message.setReceiver(new Party(to)); message.setCPAId( "http://www.flutebank.com/agreements/agreementwithofficemin.xml"); message.setConversationId("www.flute.com/orders/829202"); message.setService(new Service("purchaseorderservice")); message.setAction("Purchaseorder");
Once the message has been created, it can be sent using the connection to the provider that was created earlier.
fluteprovider.send(message);
Note that this invocation does not return anything; the provider simply accepts the message and delivers it later. The significant difference between sending messages using a provider and using the SOAPConnection discussed earlier is that no endpoint information is passed in the former. The model of "self-addressed messages" that carry the "from-to" information in a standard format as a part of the message is central to the concept of asynchronous messaging. Only self-addressed messages can be routed independently of the infrastructure. For example, the "from-to" address is contained on letters, which does not tie the postal service to a particular train or airplane route.
Let us now look at a detailed messaging example involving asynchronous communication between two organizations. We will continue to use the business use case of flutebank.com and officemin.com already covered with JavaMail and synchronous messaging. Using its JAXM provider, the Flute Bank application sends a purchase order as an ebXML message to OfficeMin asynchronously. The Flute Bank provider takes on the responsibility of delivering the message to the eventual recipient registered with it-in this case, OfficeMin's JAXM provider- which accepts the message on behalf of OfficeMin's Web service. The service processes the message and sends an ebXML message back to Flute Bank later, along with the invoice.
This provider-to-provider communication, where both sides have a role to initiate and are also in a responder mode, is typical of XML messaging across enterprise boundaries. Figure 11.32 shows how such an application can be implemented. Multiple components are involved, and to understand the workings, let us look at each in detail:
AsynClient. The Flute Bank application that sends a purchase order asynchronously
PurchaseOrderService. The OfficeMin service that handles the order and sends an invoice to Flute Bank
CallbackProcessor. The Flute Bank service that asynchronously handles invoices returned by OfficeMin
AsyncClient, shown in Listing 11.9, is the message initiator and is implemented using the guidelines discussed previously for constructing an asynchronous client (Figure 11.33 shows how the mapping is realized). AsyncClient connects to a provider, constructs an ebXML message, and sends it. Not that it does not use any physical destination (URL) anywhere in the code. The mapping of the destination to the physical endpoint is configured in the JAXM provider, using administrative tools such as the console shown in Figure 11.34.
package com.flutebank.jaxmservice; import java.io.*; import javax.servlet.http.*; import javax.servlet.*; import javax.xml.messaging.*; import javax.xml.soap.*; import javax.activation.DataHandler; // ebXML package import com.sun.xml.messaging.jaxm.ebxml.*; public class AsyncClient extends HttpServlet { private ProviderConnection fluteprovider; private MessageFactory messageFactory; // source and destination endpoints for messages private String from = "http://www.flutebank.com/ordersupplies"; private String to = "http://www.officemin.com/processorders"; private ServletContext context; public void init(ServletConfig servletConfig) throws ServletException { try { super.init(servletConfig); context = servletConfig.getServletContext(); ProviderConnectionFactory providerFactory = ProviderConnectionFactory.newInstance(); fluteprovider = providerFactory.createConnection(); ProviderMetaData metaData = fluteprovider.getMetaData(); messageFactory = fluteprovider.createMessageFactory("ebxml"); } catch (JAXMException e) { // handle exception in connecting to provider System.out.println("Exception >> " + e); } } /** * Invoked from browser when client makes a request. The method constructs * an ebXML message containing a purchaseorder.xml as the attachment. It * then sends the message to its JAXM provider, which sends the message to * officemin.com's JAXM provider, based on a URI to URL mapping. * The flutebank.com JAXM provider has an endpoint mapping that maps the URI * http://www.officemin.com/processorders to the URL * http://machineB:8081/jaxm-provider/receiver/ebxml */ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException { // create ebXML message for fluteprovider try { // create ebXML message EbXMLMessageImpl message = (EbXMLMessageImpl)messageFactory.createMessage(); // set send and receive provider message.setSender(new Party(from)); message.setReceiver(new Party(to)); message.setCPAId( "http://www.flutebank.com/agreements/agreementwithofficemin.xml"); message.setConversationId("www.flute.com/orders/829202"); message.setService(new Service("purchaseorderservice")); message.setAction("Purchaseorder"); // create and add an attachment to the message AttachmentPart attachment = message.createAttachmentPart( new DataHandler(context.getResource("/WEB-INF/purchaseorder.xml"))); attachment.setContentType("text/xml"); message.addAttachmentPart(attachment); message.saveChanges(); // send message from fluteprovider to Web service fluteprovider.send(message); // display HTML confirmation message to client ServletOutputStream out = response.getOutputStream(); out.println("<html>"); out.println("<body> <b>Order placed</b> <hr> <pre>"); message.writeTo(out); out.println("</pre></body></html>"); out.flush(); } catch (Exception e) { // handle exception in using message provider System.out.println("Exception in service " + e); } } /** * Servlets destroy method. Typically used to release any resources */ public void destroy(){ try{ fluteprovider.close(); }catch (JAXMException e) { System.out.println("Exception in service " + e); } } }
The JAXM service to receive messages, PurchaseOrderService, is similar to the synchronous version developed earlier. The significant difference is that the servlet realizing the asynchronous service does not return a message. It also implements the javax.xml.messaging.OnewayListener interface, which contains a single method invoked when the message is delivered to the service by the provider:
public void onMessage(SOAPMessage message);
The behavior and signature are identical to its JMS counterpart, javax.jms.MessageListener interface, which has a similar method:
public void onMessage(Message message);
Figure 11.35 shows an implementation scheme for an asynchronous service. Again, keep in mind that JAXMServlet is only a utility class, and extending it is not a specification requirement. The sample implementation acts in dual roles, both as a server processing messages and as a client, by sending an invoice using its own JAXM provider. When doing the latter, its behavior is identical to the AsyncClient that initiated the business conversation. Listing 11.10 shows the code for PurchaseOrderService.
package com.supplier.messageprocessor; import java.io.*; import javax.servlet.http.*; import javax.servlet.*; import javax.xml.messaging.*; import javax.xml.soap.*; // ebXML packages import com.sun.xml.messaging.jaxm.ebxml.*; import java.io.PrintWriter; import javax.xml.transform.stream.StreamSource; import javax.activation.*; /** * This JAXM service on officemin.com receives an ebXML message containing a purchase order from the sender, flutebank.com. * It responds to the sender asynchronously with an invoice. * It sends the message to a URI http://www.flutebank.com/handleinvoices that is registered in officemin.com's provider * and maps to a URL http://machineA:8080/ */ public class PurchaseOrderService extends JAXMServlet implements OnewayListener { private ProviderConnection officeminprovider; private MessageFactory messageFactory; // source and destination endpoints for messages private String from = "http://www.officemin.com/processorders"; private String to = "http://www.flutebank.com/handleinvoices"; // setup connection to message provider private ServletContext context; public void init(ServletConfig servletConfig) throws ServletException { try { super.init(servletConfig); context = servletConfig.getServletContext(); // establish connection to provider ProviderConnectionFactory providerFactory = ProviderConnectionFactory.newInstance(); officeminprovider = providerFactory.createConnection(); } catch (JAXMException e) { // handle exception in connecting to provider System.out.println("Exception >> " + e); } } // end method init /** * This is the method called by officemin.com's JAXM provider when * it receives an ebXML message marked with a destination that * matches this client's URI - http://www.officemin.com/processorders * The client specifies its URI in the client.xml file. This method receives * the message and prints the attachment (which * is the purchaseorder.xml sent by flutebank) to the console. It then sends * a message to flutebank.com (through * the mapping defined in officemin.com's provider to flutebank.com's provider) * containing an invoice.xml as the attachment. The officeMin.com JAXM provider * contains a URI to URL mapping that maps the URI * http://www.flutebank.com/handleinvoices to the URL * http://machineA:8081/jaxm-provider/receiver/ebxml * which corresponds to the URL for the JAXM provider on flutebank.com */ public void onMessage(SOAPMessage soapMessage) { try { // insert some business logic to process the message // for now we just dump it to the console and a file soapMessage.writeTo(System.out); soapMessage.writeTo(new FileOutputStream("/temp/receivedonofficemax.txt")); // send an ebXML. In this case this service is now acting in a client role sendMessageBack(); } catch (Exception e) { System.out.println("Exception in PoService " + e); } } /** Private method for constructing and sending an ebXML message * to flutebank.com's provider */ private void sendMessageBack() { try { // create ebXML message for officeminprovider messageFactory = officeminprovider.createMessageFactory("ebxml"); // create ebXML message EbXMLMessageImpl message = (EbXMLMessageImpl)messageFactory.createMessage(); // set send and receive provider message.setSender(new Party(from)); message.setReceiver(new Party(to)); // set other ebXML message headers message.setCPAId("http://www.officemin.com/agreements/agreementwithflute.xml"); message.setConversationId("www.flute.com/orders/829202"); message.setService(new Service("invoiceservice")); message.setAction("Invoice"); // Add the attachment with an invoice AttachmentPart attach = message.createAttachmentPart (new DataHandler(context.getResource("/WEB-INF/invoice.xml"))); attach.setContentType("text/xml"); message.addAttachmentPart(attach); message.saveChanges(); //send message from officeminprovider to Web service officeminprovider.send(message); } catch (Exception e) { System.out.println("Exception in PoService " + e); } }// end sendMessage /** Servlets destroy method */ public void destroy(){ try{ officeminprovider.close(); }catch (JAXMException e) { System.out.println("Exception in service " + e); } } }
The third component is the CallbackProcessor service, which is deployed along with AsyncClient on Flute Bank's servers. AsyncClient simply sends the message without expecting any reply. From a business exchange perspective, however, the recipient may need to send a message back with the response-that is, the "Asynchronous messaging with response" or "Asynchronous messaging with acknowledgment" pattern discussed earlier in Figures 11.25 and 11.26. Listing 11.11 shows the code for CallbackProcessor that implements the OneWayListener and simply displays the attachments (i.e., the invoice) for now.
package com.flutebank.jaxmservice; import java.io.*; import javax.servlet.http.*; import javax.servlet.*; import javax.xml.messaging.*; import javax.xml.soap.*; import javax.xml.transform.stream.*; import javax.xml.transform.*; import java.util.Iterator; /**Class to handle the asynchronous returns from officemin.com's JAXM provider * installed on a different machine on the network. officemin.com received the * purchaseorder.xml sent by flutebank.com and responds asynchronously * with an invoice.xml to the sender (flutebank.com). This client is registered * with flutebank.com's JAXM provider to handle these messages. */ public class CallbackProcessor extends JAXMServlet implements OnewayListener { public void init(ServletConfig servletConfig) throws ServletException { try { super.init(servletConfig); ProviderConnectionFactory providerFactory = ProviderConnectionFactory.newInstance(); ProviderConnection fluteprovider = providerFactory.createConnection(); ProviderMetaData metaData = fluteprovider.getMetaData(); } catch (JAXMException e) { // handle exception in connecting to provider System.out.println("Exception >> " + e); } } /** * Method from OneWayListener interface. Implemented by this class for * receiving callbacks. Notice the void return. Called by superclass during * its service method. It dumps the entire message to * /temp/callbackreceived received.txt file and also shows the attachment in * the message (invoice.xml) on the console * @param message, the SOAPMessage object received */ public void onMessage(SOAPMessage message) { try { message.writeTo(new FileOutputStream("/temp/callbackreceivedonflute.txt")); Iterator attachments = message.getAttachments(); while(attachments.hasNext()){ AttachmentPart part= (AttachmentPart)attachments.next(); // use a null transformer for dumping attachment to console. // See Chapter 9 for details Transformer nulltransformer = TransformerFactory.newInstance().newTransformer(); nulltransformer.transform((StreamSource)part.getContent(), new StreamResult(System.out)); } } catch (Exception e) { System.out.println("Exception occurred in onMessage" + e); } } }
We have not covered an important deployment detail. An asynchronous JAXM client application consists of one or more Web components deployed in the servlet container. Java WSDP uses an XML descriptor that accompanies each service and resides in WEB-INF/classes. This descriptor contains a mapping of a logical endpoint (URI) and a physical endpoint (URL) that describe any services that act as clients to the provider and receive messages. For example, the descriptor for Flute Bank looks like this:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE ClientConfig PUBLIC "-//Sun Microsystems, Inc.//DTD JAXM Client//EN" "http://java.sun.com/xml/dtds/jaxm_client_1_0.dtd"> <ClientConfig> <Endpoint> http://www.flutebank.com/handleinvoices </Endpoint> <CallbackURL> http://MachineA:8080/flutejaxm/callback/callbackhandler </CallbackURL> <Provider> <URI>http://java.sun.com/xml/jaxm/provider</URI> <URL>http://127.0.0.1:8081/jaxm-provider/sender</URL> </Provider> </ClientConfig>
A similar descriptor must be deployed on the OfficeMin server. These mappings correspond to items 3 and 4 in Figure 11.33. To help the reader visualize the message exchange, Listings 11.12 and 11.13 show the underlying ebXML messages transmitted on the wire.
------=_Part_3_3866500.1031591037579
------=_Part_8_176822.1031591108715 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header> <eb:MessageHeader xmlns:eb="http://www.ebxml.org/namespaces/messageHeader" eb:version="1.0" soap-env:mustUnderstand="1"> <eb:From> <eb:PartyId eb:type="URI">http://www.officemin.com/processorders</eb:PartyId> </eb:From> <eb:To> <eb:PartyId eb:type="URI">http://www.flutebank.com/handleinvoices</eb:PartyId> </eb:To> <eb:CPAId>http://www.officemin.com/agreements/agreementwithflute.xml</eb:CPAId> <eb:ConversationId>www.flute.com/orders/829202</eb:ConversationId> <eb:Service eb:type="">invoiceservice</eb:Service> <eb:Action>Invoice</eb:Action> <eb:MessageData> <eb:MessageId>687f2de1-586a-4391-94f9-c9795a6dd0b4</eb:MessageId> <eb:Timestamp>1031591037579</eb:Timestamp></eb:MessageData> </eb:MessageHeader> </soap-env:Header> <soap-env:Body/> </soap-env:Envelope> ------=_Part_8_176822.1031591108715 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <!--edited by Sameer Tyagi (none)-- > <invoice xmlns="http://www.officemin.com/schema" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.officemin.com/schema" xsi:schemaLocation="http://www.officemin.com/schema invoice.xsd"> <purchaseorder> <identifier>87 6784365876JHITRYUE</identifier> <date>29 October 2002</date> </purchaseorder> <items> <item> <quantity>3</quantity> <productnumber>229AXH</productnumber> <description>High speed photocopier machine with automatic sensors </description> <unitcost>1939.99</unitcost> </item> <item> <quantity>1</quantity> <productnumber>1632</productnumber> <description>One box of color toner cartridges</description> <unitcost>43.95</unitcost> </item> </items> <paymentdetails> <invoicenumber>8912737821ATYWER </invoicenumber> <currency>USD</currency> <total>5864</total> <payabletoo>officemin.com</payabletoo> <mailingaddress> 64 Bit True Unix Street, Windsor, CT, USA</mailingaddress> </paymentdetails> </invoice> ------=_Part_8_176822.1031591108715--
Content-Type: text/xml
<?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header> <eb:MessageHeader xmlns:eb=http://www.ebxml.org/namespaces/messageHeader eb:version="1.0" soap-env:mustUnderstand="1"> <eb:From> <eb:PartyId eb:type="URI"> http://www.flutebank.com/ordersupplies </eb:PartyId> </eb:From> <eb:To> <eb:PartyId eb:type="URI"> http://www.officemin.com/processorders </eb:PartyId> </eb:To> <eb:CPAId> http://www.flutebank.com/agreements/agreementwithofficemin.xml </eb:CPAId> <eb:ConversationId>www.flute.com/orders/829202</eb:ConversationId> <eb:Service eb:type="">purchaseorderservice</eb:Service> <eb:Action>Purchaseorder</eb:Action> <eb:MessageData> <eb:MessageId> 89fcfba5-fac8-4ddd-94b1-ba74339d42de </eb:MessageId> <eb:Timestamp>1031591106992</eb:Timestamp> </eb:MessageData> </eb:MessageHeader> </soap-env:Header> <soap-env:Body/> </soap-env:Envelope> ------=_Part_3_3866500.1031591037579 Content-Type: text/xml <?xml version="1.0" encoding="UTF-8"?> <purchaseorder xmlns="http://www.flutebank.com/schema" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.flutebank.com/schema" xsi:schemaLocation="http://www.flutebank.com/schema purchaseorder.xsd"> <identifier>87 6784365876JHITRYUE</identifier> <date>29 October 2002</date> <billingaddress> <name>FluteBank Inc</name> <street>256 Eight Bit Lane</street> <city>Burlington</city> <state>MA</state> <zip>01803-6326</zip> </billingaddress> <items> <item> <quantity>3</quantity> <productnumber>229AXH</productnumber> <description>High speed photocopier machine with automatic sensors </description> <unitcost>1939.99</unitcost> </item> <item> <quantity>1</quantity> <productnumber>1632</productnumber> <description>One box of color toner cartridges</description> <unitcost>43.95</unitcost> </item> </items> </purchaseorder> ------=_Part_3_3866500.1031591037579--
The EJB 2.1 specifications developed under the JCP as JSR-153 are a required part of the J2EE 1.4 platform and define how a message-driven EJB can realize a JAXM service. A message-driven bean (MDB) was initially intended to be a consumer of JMS messages from queues and topics sent from JMS clients. A message-driven bean is completely decoupled from any clients, in the sense that a client cannot access a message-driven bean through its EJB interfaces. Message-driven beans do not have a home, local home, or remote or local interface. They are intended to receive messages and serve as an abstraction layer for asynchronous processing in J2EE. Message-driven beans are completely stateless, in the sense that they hold no conversational state. Therefore, multiple instances of the bean can process multiple messages concurrently.
Based on the same lines as its servlet counterpart, the message-driven bean can implement the javax.xml.messaging.ReqRespListener or the javax.xml.messaging.OnewayListener interface, which specifies synchronous or asynchronous behavior for the EJB respectively. The advantage of using an EJB as the listener is that it leverage the transactional services of the EJB container upon receipt of the message.
At the time of writing, EJB 2.1 specifications were in proposed final draft state.
<SOAP-ENV:Envelope xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/ xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"> <SOAP-ENV:Header> <eps:endpoints SOAP-ENV:mustUnderstand="1" xmlns:eps="http://schemas.biztalk.org/btf-2-0/endpoints" xmlns:agr="http://www.trading-agreements.org/types/"> <eps:to> <eps:address xsi:type="agr:department">Accounting </eps:address> </eps:to> <eps:from> <eps:address xsi:type="agr:organization">Flute Bank</eps:address> </eps:from> </eps:endpoints> <prop:properties SOAP-ENV:mustUnderstand="1" xmlns:prop="http://schemas.biztalk.org/btf-2-0/properties"> <prop:identity> uuid:74b9f5d0-33fb-4a81-b02b-5b760641c1d6 </prop:identity> <prop:sentAt>2002-09-14T03:00:00+08:00</prop:sentAt> <prop:expiresAt>2003-12-30T04:00:00+08:00</prop:expiresAt> <prop:topic> http://www.flutebank.com/schema purchaseorder.xsd </prop:topic> </prop:properties> </SOAP-ENV:Header> <SOAP-ENV:Body> <purchaseorder xmlns="http://www.flutebank.com/schema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:po="http://www.flutebank.com/schema" xsi:schemaLocation="http://www.flutebank.com/schema purchaseorder.xsd"> <!--Other elements and data from the purchase order, as in previous examples--> </purchaseorder> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Two parties exchanging business messages using disparate hardware and software platforms who wish to interoperate must agree on the following:
A means to specify, package, publish, and exchange both structured and unstructured information across application or enterprise boundaries
Application-level communication protocols for communicating this information
Mechanisms for securing messages for integrity, privacy, and nonrepudiation.
Because the messages are structured in XML, passed in a standard format based on the SOAP specification, and use a standard protocol such as HTTP, message senders and consumers are somewhat interoperable with each other. A JAXM service must be able to consume SOAP 1.1 with attachment messages. The service is unaware of the source of the messages and could be generated by an application using any technology.
However, the larger picture remains. Both parties have to agree upon and understand the structure of the information in a business context as well as the semantics of how the exchange takes place. This can be achived only by agreeing on the messaging profile layered on top of messaging providers. As long as the client and the service agree on the profile, they do not necessarily need to use the same SOAP provider or even be written in the same language. Only the packaging of the message (i.e., the SOAP envelope) must be standardized. Figure 11.37 shows a possible interoperability scenario. For a JAXM client or service to interoperate with a service or client using a different provider, the parties must use the same transport bindings (the same transport protocol, such as HTTP) and the same profile in constructing the SOAP message being sent.