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.
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.
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="<firstname.lastname@example.org>" Content-Length: NNNN --MIME_boundary Content-Type: text/xml; charset=UTF-8 Content-Transfer-Encoding: 8bit Content-ID: <email@example.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="firstname.lastname@example.org"/> <interiorImage href="email@example.com"/> </realProperty:propertyListing> </SOAP-ENV: Body> </SOAP-ENV: Envelope> --MIME_boundary Content-Type: image/jpeg Content-ID: <firstname.lastname@example.org> ....JPEG DATA ..... --MIME_boundary Content-Type: image/jpeg Content-ID: <email@example.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
firstname.lastname@example.org 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
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
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 = "email@example.com"; SOAPElement frontImRef = listingElement.addChildElement("frontImage"); Name hrefAttName = soapFactory.createName("href"); frontImRef.addAttribute(hrefAttName, frontImageID); String interiorID = "firstname.lastname@example.org"; 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
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);