RPC-style Web services aim to expose a business object as a Web service interface described by WSDL (Web Services Description Language). On the other hand, document-based Web services are based on the exchange of XML documents between two parties. The Web service receiving the document is responsible for performing actions based on the
content of the XML document. The benefits of using a document-style Web service are:
They facilitate exchange of self-describing documents that have a business context (ebXML) instead of exchange of data structures that reflect application interfaces.
Document-based Web services support synchronous and asynchronous interactions.
Since changing a few fields in a document doesn't break the contract between the two
parties as changing the application interface would, document-based Web services provide
better insulation from changes to the underlying service.
JAXM (Java API for XML Messaging) is a Java Community Process project (JSR 67) that defines a Java API for exchanging business documents. JAXM aims to provide a SOAP-based infrastructure for building, sending, and receiving messages. It provides an abstraction for transport protocols (HTTP, SMTP, and FTP) and business messaging protocols (ebXML). The actual protocols used for conducting electronic business depend upon the implementation of JAXM.
Complex e-business applications are a combination of synchronous and asynchronous interactions. Consider, for example, Acme Consulting Company. Acme allows its consultants to bill their clients by using a Web interface (see Figure 1). The interaction between the consultant and the Web application is essentially synchronous in nature. Acme's Web application interfaces with its billing system. On receiving the billing information, the billing system performs a series of operations (e.g., getting customer information from the ERP system) that can take a considerable amount of time. To improve the user experience by reducing response times, an interaction like the one between the Web application and the billing system can be modeled as an asynchronous interaction. In this article, we'll use the Acme example (see Figure 1) to demonstrate how JAXM can build document-based interactions that can be either synchronous or asynchronous.
JAXM Basics
Figure 2 illustrates the fundamental elements of the JAXM architecture.
JAXM Service
A JAXM service consumes JAXM messages sent by a JAXM client. To develop a JAXM service, developers extend the class javax.xml.messaging.JAXMServlet and implement either javax.xml.messaging.One wayListener or the javax.xml.messaging. ReqRespListener interface. The choice of interface implemented depends upon whether the interaction between the client and the server is asynchronous (Oneway Listener) or synchronous (ReqRespListener) in nature.
JAXM Client
Clients exchange messages with JAXM services. JAXM clients can either directly interact with a JAXM service or go through a JAXM provider. JAXM clients that interact directly with a JAXM service can only participate in synchronous interactions. JAXM clients that use a messaging provider can participate in asynchronous as well as synchronous interactions with the JAXM service.
JAXM Messaging Provider
A JAXM messaging provider is responsible for managing the routing of messages for a JAXM client. Besides providing a degree of separation between a JAXM client and service, a provider can also offer additional services like reliable messaging, security, and transaction support. Currently, clients that want to interact asynchronously with a JAXM service have to use a provider.
JAXM Message
All JAXM messages conform to the SOAP 1.1 and SOAP with Attachments standards. JAXM messages also support the concept of higher-level protocols like ebXML through the concept of messaging profiles. A messaging profile defines the messaging contract between two parties. A JAXM client and service that share a message profile like ebXML can conduct business by exchanging ebXML messages.
Developing Asynchronous Interactions
JAXM clients that want to interact asynchronously with a JAXM service have to use a messaging provider, and must run in a servlet container or an EJB container as a message-driven bean. This restriction allows the messaging provider to notify the client when the JAXM service has finished processing their request.
In our example, the controller servlet (acme.ControllerServlet) is a JAXM client that asynchronously exchanges billing information with the billing system (see Figure 3) through a JAXM service (acme .BillProcessingServlet).
The controller servlet creates a connection to a JAXM provider during initialization as shown in Listing 1. The controller servlet uses the default implementation of the JAXM provider that is bundled with the JAXM reference implementation. Clients can also retrieve JAXM providers that are bound to a JNDI context.
Before sending a message, the controller servlet selects a JAXM profile that is supported by the provider. A list of supported profiles can be obtained from the ProviderMetaData class instance that is obtained by invoking the getMetaData method on the ProviderConnection class (see Listing 2).
In Acme's case, the controller servlet uses the SOAP-RP profile provided by the reference implementation. SOAP-RP provides an implementation of the Web Services Routing Protocol that supports messaging between applications through an intermediary, like a JAXM provider.
Next, the controller servlet uses a custom MessageFactory to create a message that supports the SOAP-RP profile.
SOAP-RPMessageImpl msg = (SOAP-RPMessageImpl)mf.createMessage();
After adding the necessary elements to the message body (see Listing 3), the controller servlet invokes the send method on the Provider Connection. The underlying XML message sent by the client is shown in Listing 4.
The send method assumes a one-way asynchronous communication between the client and the service. Invoking the send method by itself doesn't guarantee successful delivery of the message to the JAXM provider. The underlying JAXM provider implementation is responsible for delivering the message successfully to the JAXM service.
The JAXM messaging provider asynchronously routes the SOAP message to the JAXM service implemented by the class acme.BillProcessingServlet. On receiving the message the JAXM service extracts information about the bill from the message (see Listing 5). This information is then passed on to the billing system. Note that since this is an asynchronous interaction, the JAXM service doesn't return any response.
Developing Synchronous Interactions
Although synchronous interactions are typically thought of as synonymous with RPC-style Web services, there are situations where document-based Web services make a better case for synchronous exchange of documents. Take the example of two parties that want to synchronously exchange ebXML documents. An RPC-style Web service that exposes a system's object model through WSDL may not be capable of exchanging ebXML documents, whereas a document-based JAXM service which is not bound to by an object model can easily lend itself to this situation.
In our example, consultants can use Acme's Web application to search for a list of bills by the name of the company (see Figure 4). The controller servlet (JAXM client) conducts the search on behalf of the consultants through a synchronous message exchange with a JAXM service (acme.BillingInformationServlet).
The controller servlet uses the default MessageFactory class to create an empty SOAP message and then populates the SOAP message body with required elements (see Listing 6).
After constructing the message, the controller servlet uses class javax.xml.soap.SOAP ConnectionFactory to create a connection and send the message as shown below:
SOAPConnection connection =
SOAPConnectionFactory.newInstance().createConnection();
java.net.URL endpoint =
new java.net.URL("http://localhost:8080/messaging/findBills");
// Send the message
SOAPMessage responseMessage = connection.call(message, endpoint);
Note the difference between the call method of the class SOAPConnection and the send method (see Listing 3) of the Provider Connection class. The call method accepts an instance of java.net. URL, which directly points to the JAXM service. The send method of the ProviderConnection class assumes that the address of the JAXM service is embedded in the SOAP message. The call method also blocks the JAXM client until the JAXM service returns a response.
The message is directly routed to the JAXM service implemented by the servlet jaxm.BillingInformationServlet.This servlet extends the class JAXMServlet and implements the ReqRespListener interface. As shown in Listing 7, the BillingInformation Servlet extracts the search criteria from the incoming SOAP message, interfaces with the Acme billing system to conduct a search and returns a SOAP message containing the search results to the JAXM client.
Example Installation Instructions
To try the example used in this article, perform the following steps:
1. Download and install Sun's Java Developers Web Service Pack (JDWSP). The software can be found at
http://java.sun.com/webservices/downloads/
webservicespack.html.
2. Unzip file Messaging.jar. This will create a folder called messaging on your hard drive.
3. Open the file build.properties in the messaging folder. Change the property jdwsp. home to point to the directory in which the JDWSP was installed.
4. Assuming that you have Ant installed on your machine, run the command Ant (from command prompt or a shell prompt) from the folder messaging.
5. Start the Tomcat servlet engine bundled with JDWSP.
6. Using the URL http://localhost:8081/jaxm-provideradmin/index.jsp, log into the JDWSP administration tool.
7. Add the following endpoint mapping to the SOAP-RP profile .
Uri: http://xyz.acme.com/processbill
URL: http://127.0.0.1:8081/jaxm-provider/receiver/soaprp
8. You can submit billing information by using the URL http://localhost:8080/messaging/submitBill.jsp.
9. You can search for bills by company name by using the URL http://localhost:8080/messaging/search.jsp.
Conclusion
JAXM provides a good starting point for developing document-based Web services that can promote exchange of information between enterprises in a loosely coupled fashion through context-sensitive documents. It provides a flexible protocol, supporting both synchronous and asynchronous interactions between participants.
JAXM is an evolving specification and still doesn't address issues like:
1. Support for asynchronous messaging without using a message profile: Currently the only way to communicate asynchronously with a JAXM service is to use a message profile like ebXML or SOAP-RP profile.
2. Better interoperability with DOM: Currently, JAXM allows the developer to set the entire content of a SOAP message by invoking the setContent method of the javax.xml.soap.SOAPPart class. There is no support for inserting XML data fragments into a SOAP message.
References
Java API for XML Messaging:
http://java.sun.com/xml/jaxm/index.html
Web Services Routing Protocol:
http://msdn.microsoft.com/library/default.asp?url=/library/
en-us/dnglobspec/html/ws-routing.asp
Author Bio
Nikhil Patil has worked in the software industry for over six years, and is a product manager at Cysive, Inc., where he works on developing the strategic vision for the Cymbio Interaction Server. A Sun Certified Java Developer, Nikhil has an MS in industrial engineering and a BE in mechanical engineering.
npatil@cysive.com
Document-Based Web Services Using JAXM by Nikhil Patil
WSJ Vol 03 Issue 4 - pg.33
Listing 1
try
{
pcf = ProviderConnectionFactory.newInstance();
pc = pcf.createConnection();
}
catch (SOAPException e)
{
throw new ServletException(e);
}
Listing 2
String[] supportedProfiles = metaData.getSupportedProfiles();
String profile = null;
for(int i=0; i < supportedProfiles.length; i++)
{
if(supportedProfiles[i].equals("soaprp"))
{
profile = supportedProfiles[i];
break;
}
}
Listing 3:
Method of ControllerServlet to Invoke A JAXM Service using a JAXM Provider.
private void submitBill(HttpServletRequest request) throws ServletException
{
try
{
// Create a message factory.
if (mf == null)
{
ProviderMetaData metaData = null;
try
{
metaData = pc.getMetaData();
}
catch (SOAPException e)
{
throw new ServletException(e);
}
// Find the SOAP-RP profie and create a MessageFactory that supports
// SOAP-RP messages.
String[] supportedProfiles = metaData.getSupportedProfiles();
String profile = null;
for(int i=0; i < supportedProfiles.length; i++)
{
if(supportedProfiles[i].equals("soaprp"))
{
profile = supportedProfiles[i];
break;
}
}
mf = pc.createMessageFactory(profile);
}
// Create a SOAP-RP message from the message factory.
SOAPRPMessageImpl msg = (SOAPRPMessageImpl)mf.createMessage();
// Set the end point.
msg.setTo(new Endpoint(submitBillURL));
SOAPPart sp = msg.getSOAPPart();
SOAPEnvelope envelope = sp.getEnvelope();
SOAPBody bdy = envelope.getBody();
// Add a root element "bill" to the soap message.
SOAPBodyElement billElement =
bdy.addBodyElement(envelope.createName("bill"));
// Add the name of the company to the message
String company = request.getParameter("company");
billElement.addChildElement(envelope.createName
("company")).addTextNode(company);
// Add the name of the company to the message
String customer = request.getParameter("customer");
billElement.addChildElement(envelope.createName
("customer")).addTextNode(customer);
// Add the name of the company to the message
String hours = request.getParameter("hours");
billElement.addChildElement(envelope.createName
("hours")).addTextNode(hours);
// Send the message
msg.writeTo(System.out);
pc.send(msg);
}
catch (Exception se)
{
se.printStackTrace();
}
}
Listing 4: Bill information as a SOAP message.
<?xml version="1.0" encoding="UTF-8"?>
<soap-env:Envelope xmlns:soap-env=
"http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Header>
<m:path xmlns:m="http://schemas.xmlsoap.org/rp">
<m:to>http://xyz.acme.com/processbill</m:to>
<m:id>2ea82d39-b48e-47f2-b85d-d94ad2cbce03
</m:id><m:fwd/><m:rev/>
</m:path>
</soap-env:Header>
<soap-env:Body>
<bill>
<company>Cysive</company>
<customer>Tom Jones</customer>
<hours>2</hours>
</bill>
</soap-env:Body>
</soap-env:Envelope>
Listing 5: Synchronous JAXM Service.
package acme;
import javax.xml.messaging.JAXMServlet;
import javax.xml.messaging.OnewayListener;
import javax.xml.soap.*;
import java.util.*;
/**
* Asynchronous JAXM Service.
*/
public class BillProcessingServlet
extends JAXMServlet
implements OnewayListener
{
/**
* Extracts billing information from the SOAP message and updates
* the billing system.
* @param message SOAP message containing billing information.
*/
public void onMessage(SOAPMessage message)
{
try
{
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody();
// Extract information about the bill from the SOAPMessage.
Iterator itr = body.getChildElements(envelope.createName("bill"));
if (itr.hasNext())
{
SOAPElement element = (SOAPElement)itr.next();
Bill bill = new Bill();
// Extract Customer Name.
String customerName = extractData(element.getChildElements
(envelope.createName("customer")));
bill.setCustomerName(customerName);
// Extract Company Name.
String company = extractData(element.getChildElements
(envelope.createName("company")));
bill.setCompany(company);
// Extract Hours.
String hours = extractData(element.getChildElements
(envelope.createName("hours")));
bill.setHours(Double.parseDouble(hours));
// Process the Bill.
BillingSystem.getInstance().process(bill);
}
}
catch (SOAPException se)
{
se.printStackTrace();
}
}
// Extracts the text value of a XML element.
private String extractData(Iterator itr)
{
if (itr.hasNext())
{
SOAPElement element = (SOAPElement)itr.next();
return element.getValue();
}
else
{
return "";
}
}
}
Listing 6:
Code Snippet from Controller Servlet to invoke a Synchronous JAXM Client.
private void findBills(HttpServletRequest request,
HttpServletResponse response
)
throws IOException, ServletException
{
String company = request.getParameter("company");
if (company == null || company.trim().equals(""))
{
request.setAttribute("error","Company Name cannot be null or empty");
sendResponse("/search.jsp", request, response);
}
else
{
try
{
// Create a SOAPMessage
SOAPMessage message = MessageFactory.newInstance().createMessage();
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody();
SOAPBodyElement bodyElement = body.addBodyElement
(envelope.createName("findBills"));
bodyElement.addChildElement(envelope.createName("company")).addTextNode(company);
// Create a SOAP Connection
SOAPConnection connection =
SOAPConnectionFactory.newInstance().createConnection();
java.net.URL endpoint = new java.net.URL("http://localhost:8080/messaging/findBills");
// Send the message
message.writeTo(System.out);
SOAPMessage responseMessage = connection.call(message, endpoint);
responseMessage.writeTo(response.getOutputStream());
}
catch (Exception se)
{
throw new ServletException(se);
}
}
}
Listing 7:
Synchronous JAXM Service which provides billing information.
package acme;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.xml.messaging.*;
import javax.xml.soap.*;
import java.util.*;
/**
* Synchronous JAXM Service.
*/
public class BillingInformationServlet
extends JAXMServlet
implements ReqRespListener
{
MessageFactory factory = null;
/**
* Returns a SOAPMessage containing a list of bills for a given
* company.
* @param message Incoming SOAPMessage containing the company name.
* @return A SOAPMessage containing a list of bills.
*/
public SOAPMessage onMessage(SOAPMessage message)
{
try
{
// Extract the name of the company.
String companyName = extractCompanyName(message);
// Search the billing system for a collection of bills for a given company.
Collection bills = BillingSystem.getInstance().getBills(companyName);
// Build and return a SOAPMessage from the collection of bills.
return buildMessage(bills);
}
catch (SOAPException ex)
{
// Catch the exception and return a SOAPMessage with a SOAPFault.
ex.printStackTrace();
return buildFault(ex);
}
}
/**
* Initializes the servlet.
* @param config Instance of ServletConfig.
* @throws javax.servlet.ServletException thrown if there is an error in intialization.
*/
public void init(ServletConfig config) throws ServletException
{
super.init(config);
try
{
factory = MessageFactory.newInstance();
}
catch (SOAPException se)
{
throw new ServletException(se);
}
}
/**
* Extracts the name of the company
*/
private String extractCompanyName(SOAPMessage message) throws SOAPException
{
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody();
// Extract the company Name
java.util.Iterator itr = body.getChildElements
(envelope.createName("findBills"));
if (itr.hasNext())
{
SOAPElement element = (SOAPElement)itr.next();
Iterator children = element.getChildElements(envelope.createName("company"));
return extractData(children);
}
return null;
}
/**
* Returns a text value of a certain element.
*/
private String extractData(Iterator itr)
{
if (itr.hasNext())
{
SOAPElement element = (SOAPElement)itr.next();
return element.getValue();
}
else
{
return "";
}
}
// Builds a SOAPMessage from a collection of bills.
private SOAPMessage buildMessage(Collection collection)
throws SOAPException
{
SOAPMessage message = factory.createMessage();
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope envelope = sp.getEnvelope();
SOAPBody bdy = envelope.getBody();
// Add the root element "bills" to the SOAPBody.
SOAPBodyElement billsElement = bdy.addBodyElement
(envelope.createName("bills"));
Iterator itr = collection.iterator();
while (itr.hasNext())
{
Bill bill = (Bill)itr.next();
// Add information about the bill to the root element "bills".
SOAPElement billElement = billsElement.addChildElement(envelope.createName("bill"));
billElement.addChildElement(envelope.createName
("company")).addTextNode(bill.getCompany());
billElement.addChildElement(envelope.createName
("customer")).addTextNode(bill.getCustomerName());
String hours = String.valueOf(bill.getHours());
billElement.addChildElement(envelope.createName
("hours")).addTextNode(hours);
String rate = String.valueOf(bill.getHourlyRate());
billElement.addChildElement(envelope.createName
("rate")).addTextNode(rate);
String total = String.valueOf(bill.getAmount());
billElement.addChildElement(envelope.createName
("total")).addTextNode(total);
}
return message;
}
// Returns SOAPMessage containing a SOAPFault.
private SOAPMessage buildFault(Exception ex)
{
try
{
SOAPMessage message = factory.createMessage();
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope envelope = sp.getEnvelope();
SOAPBody bdy = envelope.getBody();
SOAPFault fault = bdy.addFault();
fault.setFaultString(ex.getMessage());
return message;
}
catch (SOAPException se)
{
se.printStackTrace();
return null;
}
}
}
Listing 8:
JAXM request for searching for bills.
<?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>
<findBills>
<company>Cysive</company>
</findBills>
</soap-env:Body>
</soap-env:Envelope>
JAXM Response for the search request.
<?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>
<bills>
<bill>
<company>Cysive</company>
<customer>Tom Jones</customer>
<hours>2.0</hours>
<rate>50.0</rate>
<total>100.0</total>
</bill>
</bills>
</soap-env:Body>
</soap-env:Envelope>
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com