While XML and SOAP are very good at describing data, many kinds of application data aren’t well-suited for XML—for example, a piece of binary data such as an image, or a CAD file that contains schematic diagrams of parts being ordered electronically. SOAP with Attachments (SwA) was born in recognition of this limitation. SwA combines the SOAP protocol with the MIME format to allow any arbitrary data to be included as part of a SOAP message. The model is exactly the same as the model used for including email attachments.
The MIME protocol allows multiple arbitrary blocks of data to be strung together in a message, with each block separated by a MIME header. The MIME headers delineate where each part begins and the previous part ends. The next example shows what a MIME header looks like. In SwA, the entire message consists of multiple MIME parts; the first part (part 0) is the SOAP envelope, and the remaining parts (1 through n) are the attachments. All parts are wrapped by the underlying protocol, as illustrated by Figure 3-3.
To construct and deconstruct SwA messages, use the Apache SOAP and
JavaMail APIs. Before running the example, note that the example
archive contains a text file called
attachment.txt. It is a simple text file that
contains the string “This is an
attachment.” There is also a file called
poWithAttachment.xml that is identical to
PO.xml, except that the root level tag is
<PurchaseOrderWithAttachment>
. The other
modification we have made is the addition of an element with the name
of attachment
. This element contains an
href
attribute:
<attachment href="cid:the-attachment"/>
This element helps identify the attachment when processing the document. Now run the example from the command line:
java GenericHTTPSWAClient -df ./poWithAttachment.xml -at attachment.txt
In addition to the purchase order, you should see the following output in the Tomcat console window:
Content-ID = the-attachment The attachment is... This is an attachment.
Let’s see the raw output from our client by
redirecting it at the SimpleHTTPReceive
servlet.
Run the following command:
java GenericHTTPSWAClient -url http://localhost:8080/examples/servlet/SimpleHTTPReceive -df ./poWithAttachment.xml -at attachment.txt
You should see the following output:
____________________________ Received request. ----------------------- SOAPAction = "urn:oreilly-jaws-samples" Host = localhost Content-Type = multipart/related; boundary="----=_Part_0_252212802.10059402721 20"; type="text/xml"; start="1730639424.1005940272280.apache-soap.nbchappell3" Content-Length = 1283 ----------------------- ------=_Part_0_252212802.1005940272120 Content-Type: text/xml; charset=utf-8 Content-Transfer-Encoding: 8bit Content-ID: <1730639424.1005940272280.apache-soap.nbchappell3> Content-Length: 869 <?xml version='1.0' encoding='UTF-8'?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org /1999/XMLSchema"> <SOAP-ENV:Header> <jaws:MessageHeader xmlns:jaws="urn:oreilly-jaws-samples"><From>Me</From><To>You </To><MessageId>9999</MessageId></jaws:MessageHeader> </SOAP-ENV:Header> <SOAP-ENV:Body> <PurchaseOrder xmlns="urn:oreilly-jaws-samples"> ... same as before ... <attachment href="cid:the-attachment"/> </PurchaseOrder> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ------=_Part_0_252212802.1005940272120 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Content-ID: the-attachment This is an attachment. ------=_Part_0_252212802.1005940272120-- ____________________________
Note the Content-Type = multipart/related
in the
HTTP header itself and the individual part boundaries and their
associated MIME headers for each of the parts.
GenericHTTPSWAClient
is identical
to GenericHTTPClient
, with the addition of the
code used to construct the MIME attachments. For that reason,
we’ll look only at the changes. First, we need an
import statement to allow the use of the
MimeBodyPart
class, which is included in the JavaMail
APIs:
import javax.mail.internet.MimeBodyPart;
Next, there are some parts we won’t get into, which
relate to parsing the command line for the attachment file and
passing the file to the constructor. The SOAP envelope is created and
populated as before, complete with the
<header>
and <body>
constructs. The message is created as usual. Here’s
the additional code for creating a MimeBodyPart
object and setting its content as the text that we read from the
attachment file:
// Build the Message. org.apache.soap.messaging.Message msg = new org.apache.soap.messaging.Message( ); //Attach any attachments if(m_attachment != null) { BufferedReader attachmentReader = new BufferedReader(new FileReader(m_attachment)); StringBuffer buffer = new StringBuffer( ); for(String line = attachmentReader.readLine( ); line != null; line = attachmentReader.readLine( )) { buffer.append(line); } MimeBodyPart attachment = new MimeBodyPart( ); attachment.setText(buffer.toString( ));
Next, we need a way for the receiver to reference the attachment. To
enable the receiver to dissect the message, we add an element in the
XML document with an href
value of
the-attachment
. We use that value for this
attachment part’s content-id
:
attachment.setHeader("Content-ID", "the-attachment");
Finally, we add the attachment part to the message and send it. Apache SOAP knows that you have added an attachment to the message and formats the message appropriately:
msg.addBodyPart(attachment); } msg.send (new java.net.URL(m_hostURL), URI, envelope); System.out.println("Sent SOAP Message with Apache HTTP SOAP Client.");
The method
PurchaseOrderAcceptor.PurchaseOrderWithAttachment( )
is similar to
PurchaseOrderAcceptor.PurchaseOrder( )
, with the
addition of the MIME code:
//import statements ... public class PurchaseOrderAcceptor { ... public void PurchaseOrderWithAttachment(Envelope requestEnvelope, SOAPContext requestContext, SOAPContext responseContext) throws SOAPException { System.out.println("Received a PurchaseOrderWithAttachment!!"); String cid = null; java.io.StringWriter writer = new java.io.StringWriter( ); // process SOAP header - nothing new here ... // process SOAP body org.apache.soap.Body body = requestEnvelope.getBody( ); java.util.Vector bodyEntries = body.getBodyEntries( ); writer.write(" Body====> "); for (java.util.Enumeration e = bodyEntries.elements(); e.hasMoreElements( );) { org.w3c.dom.Element el = (org.w3c.dom.Element)e.nextElement( ); org.apache.soap.util.xml.DOM2Writer.serializeAsXML( (org.w3c.dom.Node)el, writer);
Remember the <attachment href="cid:the-attachment"/>
element that we put into
poWithAttachment.xml? We now use it to find the
attachment. First, we retrieve the
<attachment>
element by name. Then we
extract the value of the content-id
stored in the
href
attribute. Once we have the ID, we can use
the getBodyPart( )
method on the
SOAPContext
object to retrieve the MIME attachment
by content-id
:
org.w3c.dom.Element attachmentEl =
(org.w3c.dom.Element)el.getElementsByTagName("attachment").item(0);
if (attachmentEl != null)
{
writer.write("
Attachment==>
");
cid = attachmentEl.getAttribute("href").substring(4);//get rid of cid:
writer.write("Content-ID = "+cid+"
");
MimeBodyPart attachment = requestContext.getBodyPart(cid);
try
{
writer.write(
"The attachment is...
"+attachment.getContent( )+"
");
}catch(Exception ex)
{
throw new SOAPException(Constants.FAULT_CODE_SERVER,
"Error writing response", ex);
}
}else
writer.write("The Content-ID is null!
");
}
System.out.println(writer.toString( ));
...
}
}
Now that we’ve had a closer look at SOAP and how it
structures XML messages and the types of processing it can perform as
part of message handling, let’s study SOAP more
deeply. SOAP message passing is important in its own right (and, as
we’ve already seen, the SOAP specification stresses
it’s message-passing foundations), but most
developers see SOAP as a mechanism for remote procedure call (RPC).
In Chapter 4, we’ll look at
SOAP-RPC in more detail and discuss the Fault mechanism and the
MustUnderstand
header.