SAAJ: No strings attached

Send and receive binary Web services content using SAAJ 1.2

At the time of this writing, most Web services consist of simple message exchanges: A client contacts a Web service and sends a message to that service. The Web service, in turn, processes that request and then sends back a reply to the client. That simple request/response pattern models the way the HTTP protocol facilitates client/Web server interactions. As with HTTP, Web service message exchanges often must include binary content, such as images, documents, or sound clips. This article introduces sending and receiving binary Web service content using SOAP (Simple Object Access Protocol) with Attachments API for Java (SAAJ) 1.2.

Before diving into the intricacies of transferring binary Web service content, it's worth pointing out that a simple request/response-style Web service contrasts with services that fashion client/server interaction as remote procedure calls, or RPCs. In an RPC, a server exposes an interface that resembles an API. In turn, a client invokes such a service by making remote calls on the service's API, passing the required parameters, and receiving the values the call produces.

XML-based RPC resembles the way you invoke objects in an object-oriented (OO) system. Indeed, when working with the Java API for XML-based RPC (JAX-RPC), you seldom become aware you are working with XML documents, not Java objects. JAX-RPC lets you think of Web services as remote objects, much as you would with Java RMI (Remote Method Invocation). The JAX-RPC runtime translates the high-level, OO method calls to the XML documents expected by the remote Web service. While RPC-style Web services often provide a more convenient programming model, RPC calls must also rely on a lower-level messaging layer to exchange the XML messages that make up the remote call.

For some Web services, it is often useful to directly program to that lower-level messaging layer. For instance, if you wish to invoke a Web service that consumes a purchase order document and returns a receipt, you can easily model that document exchange as a single request/response message exchange. Instead of making remote method invocations, you would construct XML messages, send those messages directly to a Web service, and process the service's XML response, if any exists. Since SOAP defines the common message format for Web service messages, you would need to construct SOAP-conformant messages, and, once the service responds, parse those SOAP response messages back into a format your program understands.

SAAJ provides a convenient library to construct and read SOAP messages, and also lets you send and receive SOAP messages across the network. SAAJ defines the namespace javax.xml.soap. The classes that reside in that package initially formed part of the Java API for XML Messaging (JAXM), but were recently separated into their own API. JAXM relies on SAAJ for SOAP message construction and manipulation, and adds message reliability and other features specific to XML messaging. Whereas SAAJ is a required component of J2EE (Java 2 Platform, Enterprise Edition) 1.4, JAXM is not. This article focuses on one of SAAJ's most useful aspects: the ability to attach binary content to a SOAP message.

The benefits of attachments

While SOAP's design center focuses on encapsulating XML documents in a message, SOAP's attachment feature extends a SOAP message to include, in addition to the regular SOAP part, zero or more attachments, as Figure 1 shows. Each attachment is defined by a MIME type and can assume any content represented as a byte stream.

Figure 1. A SOAP message with attachments

SOAP's attachment feature proves most useful when a client wishes to transmit binary data, such as an image or audio data, to a Web service. Without SOAP attachments, sending a piece of binary data would prove more difficult. For instance, a client's SOAP message could convey the binary file's URL address. The client would then have to operate an HTTP server to let the Web service retrieve that file. That would represent an undue burden on any Web service client, especially on clients running on limited-resource devices such as digital cameras or scanners. SOAP's attachment capability lets any Web service client able to transmit SOAP messages embed binary files directly in a SOAP message.

SOAP attachments, for instance, prove handy when interacting with portal Websites. Consider a real estate agency network that needs to distribute descriptions and photographs of homes for sale to a centralized real estate search portal. If the portal operates a servlet allowing the posting of SOAP messages with attachments, a real estate agency could update its listings with a few SOAP messages, including photos of those homes. The SOAP message body might embed the property description, and SOAP attachments could carry the image files. Under that scenario, when a portal operator's servlet receives such a message, it would return an acknowledgment document, indicating the post's availability on the portal. Figure 2 illustrates such a Web service.

Figure 2. A real estate Web service, using SOAP with attachments

The anatomy of SOAP with attachments message

The SOAP Messages with Attachments W3C (World Wide Web Consortium) Note (see Resources) does not add new features to SOAP. Rather, it defines how to take advantage of MIME types in a SOAP message to define attachments, and how to reference those attachments from within the SOAP body.

The MIME type multipart/related defines documents consisting of multiple related parts. SOAP messages with attachments must follow the multipart/related MIME type. The example below shows a multipart/related SOAP message, bound to the HTTP protocol, with two attachments:

POST /propertyListing HTTP/1.1
Host: www.realproperties.com
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml; start="<property1234@realhouses.com>"
Content-Length: NNNN
--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <property1234.xml@realhouses.com>
<?xml version='1.0'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
    <realProperty:propertyListing id="property_432"
      xmlns:realProperty="http://schemas.realhouses.com/listingSubmission">
       <listingAgency>Really Nice Homes, Inc.</listingAgency>
      <listingType>Add</listingType>
      <propertyAddress>
        <street>1234 Main St</street>
        <city>Pleasantville</city>
        <state>CA</state>         
        <zip>94323</zip>
      </propertyAddress>
       <listPrice>
         250000
       </listPrice>
      <frontImage href="property1234_front.jpeg@realhouses.com"/>
       <interiorImage href="property1234_interior.jpeg@realhouses.com"/>
    </realProperty:propertyListing>
</SOAP-ENV: Body>
</SOAP-ENV: Envelope>
--MIME_boundary
Content-Type: image/jpeg
Content-ID: <property1234_front.jpeg@realhouses.com>
....JPEG DATA .....
--MIME_boundary
Content-Type: image/jpeg
Content-ID: <property1234_interior.jpeg@realhouses.com>
....JPEG DATA .....
--MIME_boundary--

The above multipart message comprises a series of MIME-headers and related data. At the root of the document is the SOAP body. Because the SOAP body contains only XML data, the MIME type of the entire message is text/xml. Following the SOAP envelope are two attachments, each corresponding to an image file sent along with the message.

A content ID identifies each attachment. The W3C Note lets either a content ID or a content location reference the attachments, but it gives preference to the former. Such content IDs act as Uniform Resource Identifier (URI) references to attachments; the SOAP 1.1 encoding rules define how to reference a resource in a SOAP message via a URI that can reference any content, not just XML (see Section 5 of SOAP 1.1 in Resources). A SOAP processor resolves those URI references as it processes the message. Based on the above example, the SOAP processor associates the element frontImage with the data section with Content ID property1234_front.jpeg@realhouses.com in the SOAP message.

Create and send a SOAP message with attachments

SAAJ lets you create and edit any part of a SOAP message, including attachments. Most of SAAJ is based on abstract classes and interfaces such that each provider can implement SAAJ in its own products. Sun Microsystems' reference implementation comes with the Java Web Services Developer Pack (JWSDP).

Since SOAP messages represent but a special form of XML documents, JAAS builds on the Document Object Model (DOM) API for XML processing. Most SOAP message components descend from the javax.xml.soap.Node interface, which, in turn, is a org.w3c.dom.Node subclass. SAAJ subclasses Node to add SOAP-specific constructs. For instance, a special Node, SOAPElement, represents a SOAP message element.

A direct result of SAAJ's reliance on interfaces and abstract classes is that you accomplish most SOAP-related tasks via factory methods. To connect your application with the SAAJ API, you first create a SOAPConnection from a SOAPConnectionFactory. For creating and editing SOAP messages, you can also initialize a MessageFactory and a SOAPFactory. MessageFactory lets you create SOAP messages, and SOAPFactory provides the methods to create individual parts of a SOAP message:

SOAPConnectionFactory spConFactory = SOAPConnectionFactory.newInstance();
SOAPConnection con = spConFactory.createConnection();
SOAPFactory soapFactory = SOAPFactory.newInstance();

With these tools in place, you can create a SOAP message a client from a real estate agency would use to send a listing update to a portal Website.

SAAJ offers several ways to create a new SOAP message. The following example shows the simplest method that creates an empty SOAP message with an envelope, and header and body in that envelope. Since you don't need a SOAP header in this message, you can remove that element from the message:

SOAPMessage message = factory.createMessage();
SOAPHeader header = message.getSOAPHeader();
header.detachNode();

Adding the XML structure to the message body proves straightforward:

SOAPBody body = message.getSOAPBody();
Name listingElementName = soapFactory.createName(
    "propertyListing", "realProperty", 
    "http://schemas.realhouses.com/listingSubmission");
SOAPBodyElement listingElement = body.addBodyElement(listingElementName);
Name attname = soapFactory.createName("id");
listingElement.addAttribute(attname, "property_1234");
SOAPElement listingAgency = listingElement.addChildElement("listingAgency");
listingAgency.addTextNode("Really Nice Homes, Inc");
SOAPElement listingType = listingElement.addChildElement("listingType");
listingType.addTextNode("add");
SOAPElement propertyAddress = listingElement.addChildElement("propertyAddress");
SOAPElement street = propertyAddress.addChildElement("street");
street.addTextNode("1234 Main St");
SOAPElement city = propertyAddress.addChildElement("city");
city.addTextNode("Pleasantville");
SOAPElement state = propertyAddress.addChildElement("state");
state.addTextNode("CA");
SOAPElement zip = propertyAddress.addChildElement("zip");
zip.addTextNode("94521");
SOAPElement listPrice = listingElement.addChildElement("listPrice");
listPrice.addTextNode("25000");

Note you add the property's unique ID as an attribute to the propertyListing element. Further, you qualify the propertyListing element with a QName, or namespace-aware name.

You can add attachments to the SOAP message in several ways. In this example, you first create elements to denote the listed property's front and interior images. Each has an href attribute designating the attachment's content ID:

String frontImageID = "property1234_front_jpeg@realhouses.com";
SOAPElement frontImRef = listingElement.addChildElement("frontImage");
Name hrefAttName = soapFactory.createName("href");
frontImRef.addAttribute(hrefAttName, frontImageID);
String interiorID = "property1234_interior_jpeg@realhouses.com";
SOAPElement interiorImRef = listingElement.addChildElement("interiorImage");
interiorImRef.addAttribute(hrefAttName, interiorID);

To easily attach the required image files to the message, use a javax.activation.DataHandler object from the JavaBeans Activation Framework. DataHandler can automatically detect the data type passed to it, and it can therefore automatically assign the appropriate MIME content type to the attachment:

URL url = new URL("file:///export/files/pic1.jpg");
DataHandler dataHandler = new DataHandler(url);
AttachmentPart att = message.createAttachmentPart(dataHandler);
att.setContentId(frontImageID);
message.addAttachmentPart(att);

Alternatively, you may be able to pass an Object, along with the correct MIME type, to createAttachmentPart(). That method resembles the first one. Internally, the SAAJ implementation will likely look for a DataContentHandler to handle the specified MIME type. If it can't find a suitable handler, createAttachmentPart() will throw an IllegalArgumentException:

URL url2 = new URL("file:///export/files/pic2.jpg");
Image im  = Toolkit.getDefaultToolkit().createImage(url2);
AttachmentPart att2 = message.createAttachmentPart(im, "image/jpeg");
att2.setContentId(interiorID);
message.addAttachmentPart(att2);

That method's drawback centers on its reliance on instantiating UI (user interface)-related classes from the AWT (Abstract Window Toolkit) library. In some headless (server) configurations such libraries might not always be correctly configured.

Regardless of what method you choose to create the attachments, the above code represents the SOAP message shown in the first listing. Since this is just a simple XML message, you may send it to a Web service in any way the service receives messages: a direct HTTP connection, JMX (Java Management Extensions), JavaSpaces, SMTP (Simple Mail Transfer Protocol), and so on. SAAJ provides a simple facility to send and receive SOAP messages via the HTTP protocol. You need only specify the message to send and the connection end-point URL:

URL end point =
    new URL("http://localhost:8080/saajtest/servlet/InvServlet");
SOAPMessage response = connection.call(message, end point);

The SOAP connection's call() method is synchronous; it blocks until it receives a reply. That reply comes in the form of a SOAP message.

Receive and process a SOAP message with SAAJ

Reading and processing a SOAP message with SAAJ resembles creating such a message. In this example, a servlet listens for incoming SOAP messages, then uses SAAJ to decide how to process them. The code snippet below shows another way to construct a SOAPMessage: by passing a set of MIME headers and an input stream to the SOAP message factory. Therefore, prior to creating a SOAP message, you must parse the HTTP request's MIME headers:

public void doPost(HttpServletRequest req, HttpServletResponse res) 
      throws ServletException, IOException {
      ...
MimeHeaders mimeHeaders = new MimeHeaders();
Enumeration en = req.getHeaderNames();
while (en.hasMoreElements()) {
    String headerName = (String)en.nextElement();
    String headerVal = req.getHeader(headerName);
    StringTokenizer tk = new StringTokenizer(headerVal, ",");
    while (tk.hasMoreTokens()){
        mimeHeaders.addHeader(headerName, tk.nextToken().trim());
    }
}
SOAPMessage message =
    messageFactory.createMessage(mimeHeaders, req.getInputStream());

With that SOAP message in hand, the servlet can process the incoming message. To retrieve the sending company's name, for instance, you might first obtain the SOAP body element and then the element corresponding to the company name:

SOAPBody body = message.getSOAPBody();
Name listingElName = soapFactory.createName(
     "propertyListing", "realProperty",
     "http://schemas.realhouses.com/listingSubmission");
Iterator listings = body.getChildElements(listingElName);
while (listings.hasNext()) {
      // The listing and its ID
     SOAPElement listing = (SOAPElement)listings.next();
     String listingID = listing.getAttribute("id");
     // The listing agency name
     Iterator ageIt = listing.getChildElements(soapFactory.createName("listingAgency"));
     SOAPElement listingAgency = (SOAPElement)ageIt.next();
     String agencyName = listingAgency.getValue();
      ...
}

The content ID associated with each attachment lets the servlet retrieve the desired photos from the SOAP message. This example saves the advertised property's front image on the filesystem. The attachment's MIME content type determines the file extensions assigned to a saved file:

Iterator frontImage = listing.getChildElements(soapFactory.createName("frontImage"));
SOAPElement front = (SOAPElement)frontImage.next();
if (front != null) {
    String frontID = front.getAttribute("href");
    Iterator attachments = message.getAttachments();
    while (attachments.hasNext()) {
        AttachmentPart att = (AttachmentPart)attachments.next();
        if (att.getContentId().equals(frontID)) {
               String contentType = att.getContentType();
             String[] parts = contentType.split("/");
               String fileExt = "UNK";
               if (parts.length > 0) {
                 fileExt = parts[1];
             }
             InputStream is = att.getDataHandler().getInputStream();
             FileOutputStream os = 
                 new FileOutputStream("/tmp/" + listingID+ "_front." + fileExt);
             byte[] buff = new byte[1024];
             int read = 0;
             while ((read = is.read(buff, 0, buff.length)) != -1) {
                 os.write(buff, 0, read);
             }
             os.flush();
             os.close();
        }
     }   
}

Once the servlet retrieves the images and processes the SOAP message's XML content, it can send a reply to the client via the HTTP request's output stream. Since the client will look for the MIME header Content-Type in the response, you must set that MIME header value on the HTTP output stream:

SOAPMessage reply = messageFactory.createMessage();
SOAPHeader header = reply.getSOAPHeader();
header.detachNode();
SOAPBody replyBody = reply.getSOAPBody();
SOAPBodyElement bodyElement = replyBody.addBodyElement(
   soapFactory.createName("ack"));
bodyElement.addTextNode("OK");
res.setHeader("Content-Type", "text/xml");
OutputStream os = res.getOuputStream();
reply.writeTo(os);
os.flush();

When the client receives the acknowledgement, it can process that message in a way similar to how the servlet processed the client's incoming message.

One caveat to this message exchange: the client and the server must be aware of each message's structure. Since a Web service provider typically publishes its message definitions in Web service registries, in the form of Web Services Description Language (WSDL) documents, a client's developer can examine the service's WSDL definitions and construct messages accordingly.

Send binary attachments in SOAP? No problem

Developers often perceive SOAP as an overly complex mechanism to perform XML-based remote procedure calls. In this article you've seen that you can use SOAP in a simple way to send and receive simple messages, and that SOAP messages can contain binary as well as XML content. Since a growing array of tools provide support for creating and parsing SOAP documents, building a simple document-exchange Web service on SOAP is convenient.

As this article showed, SAAJ does not tie constructing and parsing SOAP messages to any specific transport used to transmit those messages. Such decoupling gives you the freedom to use the message exchange mechanism that best fits your application. For instance, if you need fault-tolerant message delivery, you might transmit SOAP messages via JAXM, Java Message Service (JMS), or JavaSpaces. If, on the other hand, you need not incur the overhead from high reliability, you might opt to pass SOAP messages through a simple HTTP connection.

Frank Sommers is founder and CEO of Autospaces, a company focused on bringing Jini technology and Web services to the automotive software market. He is also editor in chief of the Newsletter of the IEEE Task Force on Cluster Computing.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more