The Java API for XML Messaging (JAXM) and the Java API for XML-based RPC (JAX-RPC) are both part of the Java Web Services Developer Pack, Winter 01 release.[10] These APIs are a key part of Sun’s plans to integrate web services interfaces into future versions of the J2EE platform. JAXM provides a common set of Java APIs for creating, consuming, and exchanging SOAP envelopes over various transport mechanisms. It is intended mainly for a document-style exchange of information because it requires the use of low-level APIs to manipulate the SOAP envelope directly. JAX-RPC provides a means for performing RMI-like Remote Procedure Calls over SOAP. In addition, JAX-RPC provides rules for such things as client code generation, SOAP bindings, WSDL-to-Java and Java-to-WSDL mappings, and data mappings between Java and SOAP.
Fundamentally, JAXM supports synchronous communications. In fact, if you don’t run your JAXM provider in a J2EE web container (i.e., it is implemented as a message-driven bean or servlet), then it supports only synchronous communications. You don’t get asynchronous exchanges unless you use the connection provider. Don’t get hung up by the “M” versus “RPC” mislabeling. You can use JAXM to exchange document- or RPC-style SOAP messages, just as you can with JAX-RPC. The real distinction between JAXM and JAX-RPC is that JAXM forces the developer to work directly with the SOAP envelope constructs, and JAX-RPC provides a high-level, WSDL-based framework that hides details of the SOAP envelope from the developer. JAX-RPC uses WSDL to generate your messages and provides an object-oriented (i.e., RMI-like) interface to the developer. JAXM doesn’t use WSDL, so the developer must construct messages by hand and send or process them explicitly. You could make an analogy in terms of database access. You can access a database using JDBC, in which case the developer must construct SQL queries and work with the details of the database schema. Or, the developer can use JDO, which hides details of the database schema from the developer and allows the developer to work with the data as a set of Java objects.
JAXM defines the
javax.xml.soap
package, which includes the APIs for
constructing and deconstructing a SOAP envelope directly, including a
MIME-encoded multipart SWA (SOAP with attachments) message. Both JAXM
and JAX-RPC share this package. Even if you only care about RPC, you
should still go through the JAXM section to understand the SOAP
Envelope APIs.
JAXM consists of two main areas. The
“messaging” capability provides a
pattern for sending and receiving SOAP messages, with or without
attachments. The SOAP packaging part provides APIs for constructing
and deconstructing SOAP and MIME envelopes. Generally, the
functionality is separated cleanly between the
javax.xml.messaging
package and
javax.xml.soap
packages.[11]
The word "messaging” means different things to different people. For some, it refers to instant messaging or email. For others, it means reliable, asynchronous transport of critical business data, such as with Java Message Service (JMS)[12] or ebXML Message Service. In JAXM, the “M” could be any or none of those things. Like a chameleon, JAXM can take on the personality of another existing messaging protocol through the use of profiles.
Don’t infer that JAXM doesn’t
support synchronous request/response interactions. JAXM can do both
asynchronous, one-way communication and a synchronous
request/response with the send()
and
call( )
methods, respectively. It can even do an
RPC call. We will see an RPC call later when we revisit the
GetBookPrice
example using JAXM.
There’s that word again—“simple.” The bare minimum runtime requirement for JAXM is that it be deployable in a J2SE environment. This requirement means that there is no dependency on anything, except for the ability to send something over HTTP and receive it via a servlet interface, as illustrated in Figure 7-1.
In this type of environment, one can’t rely on
having a JNDI store available. Therefore, instead of performing a
lookup( )
to obtain a connection, you can get a
connection by calling the static
newInstance()
method on the
javax.xml.soap.SOAPConnectionFactory
object:
SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance( ); SOAPConnection connection = scf.createConnection( );
For sending messages in this kind of environment, we use the
javax.xml.soap.SOAPConnection.call( )
method. We
will encounter this method again later.
Receiving the message is fairly straightforward. To receive a
message, the application implements the onMessage( )
method. In a simple servlet environment,
JAXMServlet.doPost()
delegates the call to
onMessage( )
. There is no concept of registering a
message listener. In the provider situation, the provider may invoke
the onMessage( )
method any way it likes, in
accordance with its own particular delivery semantics:
public class ReceivingServlet extends JAXMServlet implements OnewayListener { ... public void onMessage(SOAPMessage msg) { System.out.println("onMessage( ) called in receiving servlet"); msg.writeTo(System.out); }
The onMessage( )
method may return
void
or return a SOAP message, depending on
whether it is intended for one-way message processing or two-way
request/response. The class that implements the onMessage( )
method must extend either the
OnewayListener
or
ReqRespListener
interface to indicate its intent.
Remember that you aren’t allowed to overload return
values; therefore, these two versions of onMessage( )
must be defined in different interfaces.
Table 7-1 shows classes and interfaces found in
the
javax.xml.soap
package. These items represent the
Envelope API, which is shared by both JAXM
and JAX-RPC. Collectively, they provide all the functionality you
need for constructing and deconstructing a SOAP or a SOAP with
Attachments envelope. In Chapter 3, we used Apache
SOAP, portions of the org.w3c.dom.DocumentBuilder
interface, and portions of the JavaMail API to accomplish the same
thing.
Table 7-1. The SOAP package
Interface/class |
Description |
---|---|
|
A single attachment to a |
|
A container for |
|
The content for a |
|
A factory used to create |
|
An object that stores a MIME header name and its value |
|
A container for |
|
A representation of an XML name |
|
A representation of a node (element) in a DOM representation of an XML document that provides tree manipulation methods |
|
An object that represents the contents of the SOAP body element in a SOAP message |
|
An object that represents the contents in a
|
|
A point-to-point connection that a client can use to send messages directly to a remote party (represented by a URL, for instance) without using a messaging provider |
|
A factory used to create |
|
The definition of constants pertaining to the SOAP 1.1 protocol
(e.g., |
|
An object representing the contents of a |
|
A factory for XML fragments that eventually end up in the SOAP part |
|
The container for the |
|
An element in the |
|
A representation of the contents in a |
|
A representation of the SOAP header element |
|
An object representing the contents in the SOAP header part of the SOAP envelope |
|
The root class for all SOAP messages |
|
The container for the SOAP-specific portion of a
|
|
A representation of a node whose value is text |
Let’s see how these classes and interfaces fit together in some working examples.
This example shows how to construct a simple SOAP message and send it to a synchronous request/reply service that is expected to respond back. Let’s start by running the sender and looking at the results. Run the following command in a command window:
java SimpleJAXMClient
This command produces the following output in the sender window:
_________________________________________________________ Starting SimpleJAXMClient: host url = http://localhost:8080/examples/servlet/SimpleJAXMReceive _________________________________________________________ Sending message to URL: http://localhost:8080/examples/servlet/SimpleJAXMReceive Received reply from: http://localhost:8080/examples/servlet/SimpleJAXMReceive Result: <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header/><soap-env:Body><Response>This is the response</Response> </soap-env:Body></soap-env:Envelope>
In the Tomcat servlet engine window, you should see:
On message called in receiving servlet There are: 0 message parts Here's the message: <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">< soap-env:Header/><soap-env:Body><Text>Some Body text</Text></soap-env:Body> </soap-env:Envelope>
Soon we’ll inspect the sending code in detail and look at the receiving code that sends the response. First, here is the sending client in its entirety:
import java.io.*; import java.util.*; public class SimpleJAXMClient { //Default values used if no command line parameters are set private static final String DEFAULT_HOST_URL = "http://localhost:8080/examples/servlet/SimpleJAXMReceive"; private static final String URI = "urn:oreilly-jaws-samples"; //Member variables private String m_hostURL; public SimpleJAXMClient(String hostURL) throws Exception { m_hostURL = hostURL; System.out.println( ); System.out.println ("________________________________________________________"); System.out.println("Starting SimpleJAXMClient:"); System.out.println(" host url = " + m_hostURL); System.out.println ("________________________________________________________"); System.out.println( ); } public void sendJAXMMessage( ) { try { javax.xml.soap.SOAPConnectionFactory scf = javax.xml.soap.SOAPConnectionFactory.newInstance( ); javax.xml.soap.SOAPConnection connection = scf.createConnection( ); // Get an instance of the MessageFactory class javax.xml.soap.MessageFactory mf = javax.xml.soap.MessageFactory.newInstance( ); // Create a message from the message factory. It already contains // a SOAP part javax.xml.soap.SOAPMessage message = mf.createMessage( ); // Get the message's SOAP part javax.xml.soap.SOAPPart soapPart = message.getSOAPPart( ); // Get the SOAP part envelope. javax.xml.soap.SOAPEnvelope envelope = soapPart.getEnvelope( ); // Get the Body from the SOAP envelope javax.xml.soap.SOAPBody body = envelope.getBody( ); // Add an element and content to the Body javax.xml.soap.Name name = envelope.createName("Text"); javax.xml.soap.SOAPBodyElement bodyElement = body.addBodyElement (name); bodyElement.addTextNode ("Some Body text"); // Send the message System.err.println("Sending message to URL: " + m_hostURL); // Synchronously send the message to the endpoint and wait for a reply javax.xml.soap.SOAPMessage reply = connection.call(message, new javax.xml.messaging.URLEndpoint (m_hostURL)); System.out.println("Received reply from: " + m_hostURL); // Display the reply received from the endpoint boolean displayResult = true; if( displayResult ) { // Dump out message response. System.out.println("Result:"); reply.writeTo(System.out); } connection.close( ); } catch(Throwable e) { e.printStackTrace( ); } } public static void main(String args[]) { ... } }
We’ll
start our examination of the code with the main( )
method. It’s not unlike the main( )
method in the other examples we have covered in the book.
This method parses incoming parameters, calls the constructor, and
then calls sendJAXMMessage( )
to do the real work:
public static void main(String args[]) { . . . // Start the SimpleJAXMClient try { SimpleJAXMClient jaxmClient = new SimpleJAXMClient(hostURL); jaxmClient.sendJAXMMessage( ); } . . . }
sendJAXMMessage( )
does all the interesting work;
it creates and populates the SOAP envelope. First, we obtain a
connection factory and use it to create a connection:
public void sendJAXMMessage( ) { try { javax.xml.soap.SOAPConnectionFactory scf = javax.xml.soap.SOAPConnectionFactory.newInstance( ); javax.xml.soap.SOAPConnection connection = scf.createConnection( );
Next, we obtain a message factory and
create an instance of a SOAP message. The simple call to
MessageFactory.createMessage( )
creates the SOAP envelope with header and
body elements already in it.
Here is an example:
javax.xml.soap.MessageFactory mf = javax.xml.soap.MessageFactory.newInstance( ); javax.xml.soap.SOAPMessage message = mf.createMessage( );
If we were to look inside the SOAP message created so far, we would see that it already has the following contents:
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header/> <soap-env:Body/> </soap-env:Envelope>
These contents are accessible using the SOAPPart
,
SOAPEnvelope
, SOAPHeader
, and
SOAPBody
objects. In JAXM, a message is accessible
using parts: either a
SOAPPart
or an
AttachmentPart
.
The SOAPPart
is the portion of the message that
contains the envelope. The envelope contains the
SOAPHeader
and the SOAPBody
:
// Get the message's SOAP part javax.xml.soap.SOAPPart soapPart = message.getSOAPPart( ); // Get the SOAP envelope. javax.xml.soap.SOAPEnvelope envelope = soapPart.getEnvelope( ); // Get the Body from the SOAP envelope javax.xml.soap.SOAPBody body = envelope.getBody( );
The pieces of the message to which we add content are
SOAPHeader
and
SOAPBody
;
we can also add content indirectly by adding attachments to the
message using the AttachmentPart
object. In a
later example, we will show how to add attachments. For now,
we’ll add some simple content to our
Body
. To do so, we must use the
addBodyElement( )
method of the
Body
object. Each body element or header element
must be associated with a Name
object, which you
obtain from the SOAPEnvelope
using the
createName( )
method. This method has two signatures: one
takes a simple String
argument, and the other
requires a String
, a prefix designation, and a URI
designation. The latter approach is intended to create an element in
a specific namespace.
After creating the Name
object and using it to
create a Body
element, we add content by calling
addTextNode( )
on the body element we just created:
// Add an element and content to the Body javax.xml.soap.Name name = envelope.createName("Text"); javax.xml.soap.SOAPBodyElement bodyElement = body.addBodyElement (name); bodyElement.addTextNode ("Some Body text");
To
execute the call, we use SOAPConnection.call( )
, passing it the message we created and a
URLEndpoint
. The URLEndpoint
object, which inherits from Endpoint
, specifies an
absolute URL as a destination. The call blocks until a response is
received:
// Send the message System.err.println("Sending message to URL: " + m_hostURL); // Synchronously send the message to the endpoint and wait for a reply javax.xml.soap.SOAPMessage reply = connection.call(message, new javax.xml.messaging.URLEndpoint (m_hostURL)); System.out.println("Received reply from: " + m_hostURL);
To dump the SOAP response from the called service, we use a
convenience method that JAXM provides: writeTo( )
. This
method sends the raw SOAP message to the specified output stream.
This method even handles attachments correctly, as
we’ll see later. When complete, we free resources by
closing the connection explicitly:
// Display the reply received from the endpoint boolean displayResult = true; if( displayResult ) { // Dump out message response. System.out.println("Result:"); reply.writeTo(System.out); } connection.close( );
The JAXM Receiver used in these examples is a simple request/reply servlet. There’s nothing profound here that we haven’t already covered. The servlet receives the SOAP message from the sender and responds with a SOAP message. The servlet code in the following listing creates a message factory during its initialization phase:
import java.io.*; import java.util.*; public class SimpleJAXMReceive extends javax.xml.messaging.JAXMServlet implements javax.xml.messaging.ReqRespListener { static javax.xml.soap.MessageFactory fac = null; static { try { fac = javax.xml.soap.MessageFactory.newInstance( ); } catch (Exception ex) { ex.printStackTrace( ); } } public void init(javax.servlet.ServletConfig servletConfig) throws javax.servlet.ServletException { super.init(servletConfig); }
Next, onMessage( )
dumps the contents of the message to the
console by using writeTo( )
; then
it constructs a new message to return to the sender. Later, we will
see how this same receiver and method can handle multipart messages
with attachments:
// This is the application code for handling the message. We simply display // the message and create and send a response. public javax.xml.soap.SOAPMessage onMessage (javax.xml.soap.SOAPMessage message) { System.out.println("On message called in receiving servlet"); try { int count = message.countAttachments( ); System.out.println("There are: " + count + " message parts"); /// Dump the raw message out System.out.println("Here's the message: "); message.writeTo(System.out); /// Construct and send SOAP message response javax.xml.soap.SOAPMessage msg = fac.createMessage( ); javax.xml.soap.SOAPPart part = msg.getSOAPPart( ); javax.xml.soap.SOAPEnvelope env = part.getEnvelope( ); javax.xml.soap.SOAPBody body = env.getBody( ); javax.xml.soap.Name name = env.createName("Response"); javax.xml.soap.SOAPBodyElement bodyElement = body.addBodyElement (name); bodyElement.addTextNode ("This is the response"); return msg; } catch(Exception e) { System.out.println("Error in processing or replying to a message"); return null; } } }
We will now show how to modify our sending client to use the JAXM API to add attachments and headers. To see the behavior and output of this new client, execute the following command:
java GenericJAXMSWAClient
This client assumes that files named PO.xml and attachment.txt are in the current directory. You should see the following output from the sending client:
_________________________________________________________ Starting GenericJAXMSWAClient: host url = http://localhost:8080/examples/servlet/SimpleJAXMReceive data file = PO.xml attachment = Attachment.txt _________________________________________________________ Sending message to URL: http://localhost:8080/examples/servlet/SimpleJAXMReceive Received reply from: http://localhost:8080/examples/servlet/SimpleJAXMReceive Result: <?xml version="1.0" encoding="UTF-8"?> <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"><s oap-env:Header/><soap-env:Body><Response>This is the response</Response></soap-e nv:Body></soap-env:Envelope>
The sender’s output is similar to the output from the previous example. The real difference is what is seen in the Tomcat console window. The same receiver we used before generates much different results because we’re now sending a multipart message. Note the MIME boundaries that separate the message’s parts. The first part of the message is the SOAP envelope; the next two parts are the added attachments:
On message called in receiving servlet There are: 2 attachment parts Here's the message: --2023334682.1010158929328.JavaMail.chappell.nbchappell3 Content-Type: text/xml <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"><s oap-env:Header/><soap-env:Body><PurchaseOrder><shipTo country="US"><name>Joe Smi th</name><street>14 Oak Park</street><city>Bedford</city><state>MA</state><zip>0 1730</zip></shipTo><items><item partNum="872-AA"><productName>Candy Canes</produ ctName><quantity>444</quantity><price>1.68</price><comment>I want candy!</commen t></item></items></PurchaseOrder></soap-env:Body></soap-env:Envelope> --2023334682.1010158929328.JavaMail.chappell.nbchappell3 Content-Type: text/plain This is an attachment. --2023334682.1010158929328.JavaMail.chappell.nbchappell3 Content-Type: text/plain; charset=ISO-8859-1 Another Part --2023334682.1010158929328.JavaMail.chappell.nbchappell3--
Here’s the code for
the new sender,
GenericJAXMSWAClient
.
We will break it down and walk through the new parts in a moment.
First, however, look at the whole thing:
import java.io.*; import java.util.*; public class GenericJAXMSWAClient { //Default values used if no command line parameters are set private static final String DEFAULT_DATA_FILENAME = "PO.xml"; private static final String DEFAULT_HOST_URL = "http://localhost:8080/examples/servlet/SimpleJAXMReceive"; private static final String URI = "urn:oreilly-jaws-samples"; private static final String DEFAULT_ATTACHMENT_FILENAME = "Attachment.txt"; //Member variables private String m_hostURL; private String m_dataFileName; private String m_attachment; public GenericJAXMSWAClient(String hostURL, String dataFileName, String attachment) throws Exception { m_hostURL = hostURL; m_dataFileName = dataFileName; m_attachment = attachment; System.out.println( ); System.out.println("_____________________________________________________"); System.out.println("Starting GenericJAXMSWAClient:"); System.out.println(" host url = " + m_hostURL); System.out.println(" data file = " + m_dataFileName); System.out.println(" attachment = " + m_attachment); System.out.println("_____________________________________________________"); System.out.println( ); } public void sendJAXMMessage( ) { try { // for doing JAXP transformations javax.xml.transform.TransformerFactory tFact = javax.xml.transform.TransformerFactory.newInstance( ); javax.xml.transform.Transformer transformer = tFact.newTransformer( ); // Create an specific URLEndpoint javax.xml.messaging.URLEndpoint endpoint = new javax.xml.messaging.URLEndpoint(m_hostURL); // Create a connection javax.xml.soap.SOAPConnectionFactory scf = javax.xml.soap.SOAPConnectionFactory.newInstance( ); javax.xml.soap.SOAPConnection connection = scf.createConnection( ); // Get an instance of the MessageFactory class javax.xml.soap.MessageFactory mf = javax.xml.soap.MessageFactory.newInstance( ); // Create a message from the message factory. // It already contains a SOAP part javax.xml.soap.SOAPMessage message = mf.createMessage( ); // Get the message's SOAP part javax.xml.soap.SOAPPart soapPart = message.getSOAPPart( ); // Get the SOAP envelope from the SOAP part of the message. javax.xml.soap.SOAPEnvelope envelope = soapPart.getEnvelope( ); // Read in the XML that will become the body in the SOAP envelope javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance( ); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder( ); org.w3c.dom.Document poDoc = db.parse(m_dataFileName); // Get the empty SOAP envelope as a generic Source // and put it into a DOMResult javax.xml.transform.Source spSrc = soapPart.getContent( ); javax.xml.transform.dom.DOMResult domResultEnv = new javax.xml.transform.dom.DOMResult( ); transformer.transform(spSrc, domResultEnv); // Now that we have the empty SOAP envelope in a DOMSource, we // need to put it together with the DOM we just built from the // input file. // Get the document org.w3c.dom.Node envelopeRoot = domResultEnv.getNode( ); if (envelopeRoot.getNodeType( ) == org.w3c.dom.Node.DOCUMENT_NODE) { // Get the root element of the document. org.w3c.dom.Element docEl = ((org.w3c.dom.Document)envelopeRoot).getDocumentElement( ); // Find the <SOAP-ENV:Body> tag using the envelope namespace org.w3c.dom.NodeList nList = docEl.getElementsByTagNameNS( javax.xml.soap.SOAPConstants.URI_NS_SOAP_ENVELOPE,"Body"); if (nList.getLength( ) > 0) { // Found our <PurchaseOrder> element. Plug it in org.w3c.dom.Node bodyNode = nList.item(0); org.w3c.dom.Node poRoot = poDoc.getDocumentElement( ); // Import the node into this document. org.w3c.dom.Node importedNode = ((org.w3c.dom.Document)envelopeRoot).importNode(poRoot, true); bodyNode.appendChild(importedNode); // Now shove it all back into the envelope. javax.xml.transform.dom.DOMSource domSource = new javax.xml.transform.dom.DOMSource(envelopeRoot); soapPart.setContent(domSource); } } else if (envelopeRoot.getNodeType( ) == org.w3c.dom.Node.ELEMENT_NODE) System.out.println("ElementNode"); else System.out.println("Unknown Node type"); // Get the Header from the SOAP envelope javax.xml.soap.SOAPHeader header = envelope.getHeader( ); // Add an element and content to the Header javax.xml.soap.Name name = envelope.createName("MessageHeader", "jaxm","urn:oreilly-jaws-samples"); javax.xml.soap.SOAPHeaderElement headerElement = header.addHeaderElement(name); // Add an element and content to the Header name = envelope.createName("From"); javax.xml.soap.SOAPElement childElement = headerElement.addChildElement (name); childElement.addTextNode ("Me"); // Add an element and content to the Header name = envelope.createName("To"); childElement = headerElement.addChildElement(name); childElement.addTextNode ("You"); // Add additional Parts to the message javax.activation.FileDataSource fds = new javax.activation.FileDataSource(m_attachment); javax.activation.DataHandler dh = new javax.activation.DataHandler(fds); javax.xml.soap.AttachmentPart ap1 = message.createAttachmentPart(dh); message.addAttachmentPart(ap1); javax.xml.soap.AttachmentPart ap2 = message.createAttachmentPart("Another Part", "text/plain; charset=ISO-8859-1"); message.addAttachmentPart(ap2); // Save the changes made to the message message.saveChanges( ); System.err.println("Sending message to URL: "+ endpoint.getURL( )); // Send the message to the endpoint and wait for a reply javax.xml.soap.SOAPMessage reply = connection.call(message, endpoint); System.out.println("Received reply from: " + endpoint); // Display the reply received from the endpoint boolean displayResult = true; if( displayResult ) { // Document source, do a transform. System.out.println("Result:"); javax.xml.soap.SOAPPart replyPart = reply.getSOAPPart( ); javax.xml.transform.Source src = replyPart.getContent( ); javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult( System.out ); transformer.transform(src, result); System.out.println( ); } connection.close( ); } catch(Throwable e) { e.printStackTrace( ); } } // // NOTE: the remainder of this deals with reading arguments // /** Main program entry point. */ public static void main(String args[]) { // Process command line, etc ... // Start the GenericJAXMSWAClient try { GenericJAXMSWAClient jaxmClient = new GenericJAXMSWAClient(hostURL, dataFileName, attachment); jaxmClient.sendJAXMMessage( ); } catch(Exception e) { System.out.println(e.getMessage( )); } } ... }
Much of the code in
this chapter deals with attaching
PO.xml
to the SOAP envelope. When trying to port our
Apache SOAP example from the previous chapter, we ran into a bit of a
gotcha. Attaching an existing XML document to a SOAP envelope is
reasonable—but you can’t do it, at least not
simply. This flaw is by far the biggest we have encountered in the
API. The MessageFactory creates an envelope with empty
<Header>
and <Body>
elements. APIs exist for creating and manipulating elements
individually, but nothing lets you take a whole document and attach
it. A SOAPPart.setContent( )
method takes a
document source and attaches it as the SOAP part of the message, but
the document that you give it must have the envelope structure in
place already. This sort of defeats the purpose. If we had corporate
data that was already packaged in full SOAP envelopes, we
wouldn’t need an API at all, would we?
Evidence suggests that this package was created solely for the purpose of providing an API for connecting to an ebXML infrastructure. In ebXML, the body of the envelope is intended to be a manifest and the actual payload of the message is intended to be an attachment. What if you don’t want to use it in that way?
Enough of the soapbox. Our solution converts both the envelope and
the XML document into DOM trees,
plugs them together, and assigns the whole thing back into the
envelope. To help do this, we use a
JAXP
Transformer
object. The
javax.xml.transform
package is an API in JAXP,
which is mainly intended for transforming documents using XSLT
stylesheets. We use it as a utility to convert the envelope and the
XML document between a DOM tree representation and the
javax.xml.transform.Stream
datatype that is
required by some JAXM envelope methods we will use:
// for doing JAXP transformations javax.xml.transform.TransformerFactory tFact = javax.xml.transform.TransformerFactory.newInstance( ); javax.xml.transform.Transformer transformer = tFact.newTransformer( );
First, read in the XML document (PO.xml) from a disk and put it into a document:
// Read in the XML that will become the body in the SOAP envelope javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance( ); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder( ); org.w3c.dom.Document poDoc = db.parse(m_dataFileName);
After creating the message and getting its SOAPPart,
getContent( )
retrieves the envelope as a generic
javax.xml.transform.Source
object. The
Source
object is the superclass of either
DOMSource
, SAXSource
, or
StreamSource
. Likewise, the
Result
object is the superclass of
DOMResult
, SAXResult
, and
StreamResult
. The transformer can take any
Source
object and do the right thing
automatically, regardless of its subtype, and convert it to the
desired Result
, as shown in the following listing:
// Get the empty SOAP Envelope as a generic Source // and put it into a DOMResult javax.xml.transform.Source spSrc = soapPart.getContent( ); javax.xml.transform.dom.DOMResult domResultEnv = new javax.xml.transform.dom.DOMResult( ); transformer.transform(spSrc, domResultEnv);
Now that we have the envelope as a DOMResult
,
retrieve the Document
and its root element:
org.w3c.dom.Node envelopeRoot = domResultEnv.getNode( ); if (envelopeRoot.getNodeType( ) == org.w3c.dom.Node.DOCUMENT_NODE) { // Get the root element of the document. org.w3c.dom.Element docEl = ((org.w3c.dom.Document)envelopeRoot).getDocumentElement( );
Next, find the
<SOAP-ENV:Body>
tag using the namespace of the envelope and
plug in the purchaseOrder
document:
// Find the <SOAP-ENV:Body> tag using the envelope namespace org.w3c.dom.NodeList nList = docEl.getElementsByTagNameNS( javax.xml.soap.SOAPConstants.URI_NS_SOAP_ENVELOPE,"Body"); if (nList.getLength( ) > 0) { // Found our <PurchaseOrder> element. Plug it in org.w3c.dom.Node bodyNode = nList.item(0); org.w3c.dom.Node poRoot = poDoc.getDocumentElement( );
Now we have the two elements that need to be attached to one another:
the SOAP Body
element and the
PurchaseOrder
element. However, you
can’t just reparent a Node
from
one document to another; doing so causes an error. Each element keeps
track of its owning Document
, which is checked by
the individual routines that insert elements. Move a
Node
into another document properly by
importing the Node
and its subelements into the
Document
first. This operation performs a copy.
The second parameter to import( )
is a
Boolean
that indicates whether this is a deep copy
of all subnodes or just the current one. Once the nodes are imported
into the Document
that represents the envelope, we
can simply attach the root Node
as the immediate
child of the <Body>
element:
org.w3c.dom.Node importedNode = ((org.w3c.dom.Document)envelopeRoot).importNode(poRoot, true); bodyNode.appendChild(importedNode);
If you use DOM level 3, there is an alternative to doing a copy. An
experimental adoptNode( )
method reassigns an
actual instance of a Node
from one
Document
to another.
Now that the envelope is joined to the PO document, we take the root
Node
, convert it to a
DOMSource
, and place the whole thing into the
messages’s SOAPPart
:
javax.xml.transform.dom.DOMSource domSource = new javax.xml.transform.dom.DOMSource(envelopeRoot); soapPart.setContent(domSource);
You will encounter no surprises here. This code is symmetric to the Body APIs that we saw at the beginning of this section:
// Add an element and content to the Header javax.xml.soap.Name name = envelope.createName("MessageHeader", "jaxm","urn:oreilly-jaws-samples"); javax.xml.soap.SOAPHeaderElement headerElement = header.addHeaderElement(name); // Add an element and content to the Header name = envelope.createName("From"); javax.xml.soap.SOAPElement childElement = headerElement.addChildElement (name); childElement.addTextNode ("Me"); // Add an element and content to the Header name = envelope.createName("To"); childElement = headerElement.addChildElement(name); childElement.addTextNode ("You");
Next,
let’s look at two (of many) ways to add attachments
to our SOAP message. The simple sender uses both methods. No matter
how you add an attachment, though, it requires two steps: call
createAttachmentPart( )
with the appropriate content to get an
attachment and addAttachmentPart( )
to add the attachment to the message.
The first method is arguably more complex; we use it to insert an
external file (in this case, a purchase order, formatted as XML) as
an attachment. First, we use the Activation Framework to create a
FileDataSource
that points at our external purchase order. We then convert the
FileDataSource
to a
DataHandler
;
in turn, we use the DataHandler to create our first attachment,
ap1
, by calling createAttachmentPart( )
. Finally, we call addAttachmentPart( )
to add the attachment to the message.
Note that we don’t need to specify the content type
anywhere; the content type is provided automatically by the
DataHandler
object:
// Add additional Parts to the message javax.activation.FileDataSource fds = new javax.activation.FileDataSource(m_attachment); javax.activation.DataHandler dh = new javax.activation.DataHandler(fds); javax.xml.soap.AttachmentPart ap1 = message.createAttachmentPart(dh); message.addAttachmentPart(ap1);
Perhaps a more intuitive way to create an attachment is to call
createAttachmentPart( )
with the content and
content type as arguments, as we’ve done here in the
attachment ap2
:
javax.xml.soap.AttachmentPart ap2 = message.createAttachmentPart("Another Part", "text/plain; charset=ISO-8859-1"); message.addAttachmentPart(ap2);
JAXM is capable of morphing itself into an API that frontends any number of SOAP-based messaging frameworks through the use of "profiles.” A key part of a message profile is the ability to automate the creation of message headers and body elements that may be specific to Framework. We will describe this concept in more detail in a moment. In the 1.0 reference implementation, an ebXML MS profile and a SOAP-RP profile are provided as examples.
A profile consists of a ProviderConnectionFactory
,
a ProviderMetaData
object that provides a list of
profiles via a getSupportedProfiles( )
method, and a custom
MessageFactory
used to create messages specific to
the profile being used. Let’s look at how these are
used.
ProviderConnectionFactory
allows a JAXM client to obtain a
ProviderConnection
to a messaging provider, such
as an ebXML Message Service, or a JMS provider that supports SOAP
over JMS. A ProviderConnectionFactory
can be
configured administratively and retrieved via a JNDI lookup( )
. From there, a ProviderConnection
is
established:
ctx = new InitialContext( ); ProviderConnectionFactory pcf = (ProviderConnectionFactory)ctx.lookup("GuaranteedMessaging"); ProviderConnection pc = pcf.createConnection( );
Once the
ProviderConnection
is instantiated, the ProviderMetaData
class can be
queried to discover whether this connection supports a desired
profile. The getSupportedProfiles( )
returns an
array of String
s that lists the profiles that the
ProviderConnection
supports. For example, if ebXML
is supported, the array will contain the string
"ebXML"
:
ProviderMetaData pMetaData = pc.getMetaData( ); String[] supportedProfiles = pMetaData.getSupportedProfiles( ); String desiredProfile = null; for(int i=0; i < supportedProfiles.length; i++) { if(supportedProfiles[i].equalsIgnoreCase("ebxml")) { desiredProfile = supportedProfiles[i]; break; } }
It is possible to plug in a custom
MessageFactory
that creates a message in the form
expected by the transport being used. For example, a
MessageFactory
for an ebXML profile might create a
message with a SOAP envelope, which is prepopulated with the
<MessageHeader>
element in the SOAP header.
In the following code, the EbXMLMessageImpl
is a
custom extension of the
javax.xml.soap.SOAPMessage
:
MessageFactory mf = pc.createMessageFactory(desiredProfile); EbXMLMessageImpl ebxmlMsg = (EbXMLMessageImpl)mf.createMessage( );
You can send a message with JAXM in two ways. One way uses the
ProviderConnection.send( )
method. The other uses
SOAPConnection.call( )
. The two methods have
different purposes and different semantics. Since we are on the
subject of ProviderConnection
, we will talk about
send( )
first and defer
SOAPConnection.call( )
to Section 7.1.2.
The ProviderConnection.send( )
method assumes that
one-way asynchronous sending can occur. Whether the send( )
method blocks and waits for the operation to occur
depends on the provider’s underlying message
delivery semantics.
If you look at the API document for ProviderConnection.send( )
, you may notice that there is no way to specify a
destination as part of the method signature. It assumes that the
destination is established and somehow already associated with the
SOAP message. There are a number of reasons for this design:
The JAXM API is intended to be agnostic with regard to the underlying
workings of the provider. The API is designed to work with many
different providers, and specifying the destination as a parameter to
send( )
may not always be appropriate.
Whether the destination is a URI, URN, or an absolute URL is a function of the underlying infrastructure to which the JAXM API is attached. For instance, the messaging provider may provide an administration piece that maps generic URIs to specific destinations such as a URL or a JMS Topic or Queue.
The SOAP header element stores the destination (or destinations). The SOAP header is constructed using the Envelope APIs (just like any other part of the message).
To facilitate setting the destination, JAXM provides an
Endpoint
class that specifies a URI as a
destination. The following code assumes that the profile-specific
message has defined additional setFrom( )
and setTo( )
methods, and
that the underlying infrastructure knows how to interpret the URI
string used to construct the Endpoint
object:
ebxmlMsg.setFrom(new Endpoint(from)); ebxmlMsg.setTo(new Endpoint(to)); pc.send(ebxmlMsg);
A strange inconsistency seems to exist in the API here: the code one
would use to connect and send SOAP messages depends on how the client
is deployed. When using a ProviderConnection
, you
send a message using the
java.xml.messaging.ProviderConnection.send( )
method. Otherwise, you send the message using the
javax.xml.soap.SOAPConnection.call( )
method. One
could argue that the two scenarios are sufficiently different and
don’t warrant consistent APIs. If you write an
application that is intended to connect to a larger framework, which
implies using the ProviderConnection
approach,
many things specific to that framework have to be coded into the
application (beyond just the connect and send operations).
[10] Sun remamed the Java XML Pack to the Java Web Services Developer Pack in February 2002. The new name is confusing—the Java XML Pack still exists and remains unchanged; the Web Services Developer Pack is the XML Pack with the addition of Tomcat, Ant, and other tools. We don’t know what name Sun is likely to use in the future, so be prepared for some confusion when you go to their web site.
[11] We say
“generally” because of the
subtleties relating to the placement of the send()
and call()
methods, which we will cover in a later
section.
[12] For more information on JMS, please refer to Java Message Service, by Richard Monson-Haefel and David Chappell (O’Reilly).