© Copyright International Business Machines Corporation 2002. All rights reserved.
SOAP with Attachments [W3C] is a useful tool for conveying binary or "raw" XML data together with SOAP messages in a Web Services environment. It can help you, for example, if you need to use Web Services to transfer the contents of a binary file, or if you need to convey a large binary object like a JPEG or CAD drawing along with a SOAP message containing instructions for processing the binary information.
SOAP with attachments gets around some of the limitations of the SOAP specification in dealing with binary data -- since all data in a SOAP message must be represented as XML, which means that binary data sent inside a SOAP envelope needs to be encoded as text so that it can be included within the XML. However, this makes the XML extremely difficult to read, as the huge base-64 encoded segments complicate debugging. So, SOAP with attachments lets you circumvent these problems by using multipart MIME messages to carry both the SOAP envelope and the binary file as MIME messages.
A great article by Joshy Joseph entitled Handling attachments in SOAP describes Apache support for SOAP with Attachments. However, implementing the concepts outlined in this article in WebSphere Studio Application Developer 4.0.X (hereafter called Application Developer) has proven to be challenging, because Application Developer has a few limitations in its support for SOAP with Attachments.
First, the Web Services Wizard in Application Developer does not support the MIME extension bindings in WSDL. For instance, if you take the WSDL files included in Joshy's article and try to import them into the Application Developer Web Services Wizard, you will get the following error message:

Failing in that path, most developers then turn to the Apache documentation and
discover the support (hinted at in Joshy's article) for serializing javax.activation.DataHandlers.
For those unfamiliar with the JavaBeans Activation Framework (JAF) [Sun], a DataHandler is a way of
wrapping an arbitrary data source so that it can be sent in a MIME message. The JAF framework is an important part
of the support for the JavaMail API, and it is also a key constituent of the SOAP with Attachments support in Apache SOAP.
According to the Apache SOAP documentation [Apache] all you have to do when using RPC-style SOAP messages
is to use the special serializers for DataHandlers (the MimePartSerializer) that are provided by Apache SOAP.
However, even though SOAP with attachments has been supported in Apache SOAP through
the special MimePartSerializer since Apache SOAP 2.1, the Web Services Wizard in Application Developer also does
not provide automatic support for the javax.activation.DataHandler mappings in Apache SOAP. If you try to include a
javax.activation.DataHandler as a parameter or return value from a method, and then run the Application Developer
Web Services Wizard, then you will find the following mappings in the Java-to-XML mappings screen:

Note the profusion of classes in the list of classes to map -- and this is for a method
that has only a String as a parameter and a javax.activation.DataHandler as a return value!
In fact, what is happening is that the Wizard is trying to treat the DataHandler as a Java bean, and is thus breaking down
the DataHandler into its constituent parts. In fact, even if you click Next on this screen,
the Wizard will fail on the following screen. (This problem continues in Application Developer 5.0 Early Availability version,
though it should be fixed in a later version.)
What we need to do is to "trick" the Application Developer Web Services Wizard into doing what we want -- build most of the Web Service for us, and then to edit the code and configuration files that it generates so that we can take advantage of the SOAP with Attachments support.
What the Web Services Wizard gives us
Why do we need to do this? Wouldn't it just be easier to build this all by hand and avoid the Wizard? Not really. The Wizard does a lot for you when you are building RPC-style Web Services from a Java bean or EJB (this is described in more depth in [Wizardry]):
- It generates the appropriate Apache SOAP deployment descriptor (an ISD file) for the Web Service.
- It can generate a "proxy" class that a client can use to invoke the Web service from a remote Java process. This class is a lot of code to create by hand, and because it's "boilerplate" code, it would be nice to have it generated automatically.
- It can generate a "sample" JSP application that lets you test the Web Service.
- It can generate a WSDL document that describes your Web service.
- It updates that classpath of the Application Developer Web Project with additional references to the needed Apache SOAP classes. This is a subtle point, and one that can cause consternation to people just getting acquainted with Web Services in Application Developer. In particular, it adds the following entries into the .classpath file:
<classpathentry kind="var" path="SOAPJAR"/> <classpathentry kind="var" path="XERCESJAR"/> <classpathentry kind="lib" path="webApplication/WEB-INF/lib/soapcfg.jar"/> <classpathentry kind="lib" path="webApplication/WEB-INF/lib/xsd.bean.runtime.jar"/> |
While you could add all of these yourself, in my opinion it's much easier to let the Wizard to it. Even though in our case we won't be able to take advantage of all of these features (for instance, as we've seen, the Wizard doesn't currently support the WSDL tags for MIME encoding) it's still worthwhile to use the Wizard in most cases.
Tricking the Web Services Wizard
Let's start by examining the class we want to make into our Web Service:
package com.ibm.soap.attachments.demo;
import javax.activation.*;
public class FileTransferService {
public FileTransferService() {}
public javax.activation.DataHandler getFileNamed(String name) {
try {
FileDataSource datasource = new FileDataSource(name);
DataHandler handler = new DataHandler(datasource);
java.io.InputStream stream = handler.getInputStream();
return handler;
} catch (Exception e) {
System.out.println("Exception caught on file " + name + " "+ e);
return null;
}
}
}
|
This is a very simple class that returns a DataHandler on a file named by the String parameter that is passed in. By making this class a Web Service, you can transfer the contents of any file from the server to the client. In other words, this service is basically doing FTP over SOAP. You could argue that there are better ways to do this (and you'd be right), but the point of this exercise is to show how to make Application Developer work with SOAP with Attachments, not to debate the merits of file transfer mechanisms.
Since we have found that the Application Developer Web Services Wizard will fail on processing the
java.activation.DataHandler, we must instead temporarily change the return type to java.lang.Object
in order to run the Web Services Wizard to create a Java bean Web Service. Then we can modify the ISD (deployment descriptor)
file and the generated proxy file to correctly use the right serializer.
You can begin by creating a new Web project in Application Developer. I named my project
SOAPWithAttachmentsProject and created it by using the Create a Web Project Wizard in Application Developer.
I then added a Java package named com.ibm.soap.attachments.demo to the source folder of the project
in the Java perspective using the New => Package Wizard from the context menu of the packages view.
Then I used New => Class, created the class FileTransferService, and then filled in
the code above. It should compile without any red "x" marks indicating errors. Next, simply change the
external interface (temporarily) by changing the method signature of the getFileNamed method from:
public javax.activation.DataHandler getFileNamed(String name)
to:
public java.lang.Object getFileNamed(String name)
|
Now your ready to run the Web Services Wizard. Switch over to the Web Perspective,
select the FileTransferService file, and choose New => Web Service. In the Wizard's first page, select
Generate a Proxy and Overwrite files without Warning and click Finish:

At this time Application Developer will publish the Web project to the default server and start the server (switching you to the Servers Perspective in the process). Go ahead and switch to the servers page in the Server Perspective and stop the server -- we're not ready to test this yet.
After stopping the server, make sure you go back and change the signature of the getFileNamed()
method so that it returns a javax.activation.DataHandler -- we're now done with the Wizard and won't use it again.
Updating the Apache deployment descriptor
The next thing you need to do is change the ISD file so that the service will correctly use the MimePartSerializer
to serialize instances of javax.activation.DataHandler. In the folder
webApplication/WEB-INF/isd/java/com/ibm/soap/attachments/demo, examine the file FileTransferService.ISD:
<root>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="http://tempuri.org/com.ibm.soap.attachments.demo.FileTransferService"
checkMustUnderstands="false">
<isd:provider type="java" scope="Application" methods="getFileNamed">
<isd:java class="com.ibm.soap.attachments.demo.FileTransferService"
static="false"/>
</isd:provider>
<isd:mappings>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="http://www.filetransferservice.com/schemas/
FileTransferServiceRemoteInterface"
qname="x:java.lang.Object"
javaType="java.lang.Object"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
</isd:mappings>
</isd:service>
</root>
|
This ISD file is using the BeanSerializer to serialize any instances of type Object in the method
getFileNamed. You need to change that so that it instead serializes instances of javax.activation.DataHandler
in the same method. Update the ISD file to match the following changes:
<root>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="http://tempuri.org/com.ibm.soap.attachments.demo.FileTransferService"
checkMustUnderstands="false">
<isd:provider type="java" scope="Application" methods="getFileNamed">
<isd:java class="com.ibm.soap.attachments.demo.FileTransferService"
static="false"/>
</isd:provider>
<isd:mappings>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="http://www.filetransferservice.com/schemas/
FileTransferServiceRemoteInterface"
qname="x:javax.activation.DataHandler"
javaType="javax.activation.DataHandler"
xml2JavaClassName="org.apache.soap.encoding.soapenc.
MimePartSerializer"
java2XMLClassName="org.apache.soap.encoding.soapenc.
MimePartSerializer"/>
</isd:mappings>
</isd:service>
</root>
|
All we've done is change the qname and javaType attributes to reflect the new type,
and update the xml2JavaClassName and java2XMLClassName attributes to reflect the right serializer class.
Updating the generated proxy class
The next step is to update the proxy class so it will return an instance of javax.activation.DataHandler
from its getFileNamed() method, and also correctly use the right serializer (similar to what we accomplished by rewriting the
deployment descriptor earlier). Luckily, our modifications are quite limited, and similar to the ones made to the deployment descriptor.
As an example, here is the getFileNamed() method from the originally generated proxy class:
public synchronized java.lang.Object getFileNamed(java.lang.String name)
throws Exception
{
String targetObjectURI = "http://tempuri.org/
com.ibm.soap.attachments.demo.FileTransferService";
String SOAPActionURI = "";
if(getURL() == null)
{
throw new SOAPException(Constants.FAULT_CODE_CLIENT,
"A URL must be specified via FileTransferServiceProxy.setEndPoint(URL).");
}
call.setMethodName("getFileNamed");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
call.setTargetObjectURI(targetObjectURI);
Vector params = new Vector();
Parameter nameParam = new Parameter("name",
java.lang.String.class, name, Constants.NS_URI_SOAP_ENC);
params.addElement(nameParam);
call.setParams(params);
Response resp = call.invoke(getURL(), SOAPActionURI);
//Check the response.
if (resp.generatedFault())
{
Fault fault = resp.getFault();
call.setFullTargetObjectURI(targetObjectURI);
throw new SOAPException(fault.getFaultCode(), fault.getFaultString());
}
else
{
Parameter refValue = resp.getReturnValue();
return ((java.lang.Object)refValue.getValue());
}
}
|
As you can see above, the only references to the return type lie in the cast of the return value from the Call object, and also in the signature of the method itself. In addition to modifying these lines, you also need to change the registration of the type of the return value. Look at the following static initializer generated in the Proxy class to place the right type-serializer mapping in the Apache SOAP Mapping registry on the client:
{
org.apache.soap.encoding.soapenc.BeanSerializer ser_1 =
new org.apache.soap.encoding.soapenc.BeanSerializer();
org.apache.soap.encoding.soapenc.BeanSerializer deSer_1 =
new org.apache.soap.encoding.soapenc.BeanSerializer();
smr.mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
new QName("http://www.filetransferservice.com/schemas/
FileTransferServiceRemoteInterface",
"java.lang.Object"),java.lang.Object.class, ser_1, deSer_1)
}
|
Our first update is to change the return value of the getFileNamed() method,
and update cast to cast the result value to a javax.activation.DataHandler:
public synchronized javax.activation.DataHandler getFileNamed
(java.lang.String name)
throws Exception
{
String targetObjectURI = "http://tempuri.org/com.ibm.soap.attachments.demo.
FileTransferService";
String SOAPActionURI = "";
if(getURL() == null)
{
throw new SOAPException(Constants.FAULT_CODE_CLIENT,
"A URL must be specified via FileTransferServiceProxy.setEndPoint(URL).");
}
call.setMethodName("getFileNamed");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
call.setTargetObjectURI(targetObjectURI);
Vector params = new Vector();
Parameter nameParam = new Parameter("name", java.lang.String.class,
name, Constants.NS_URI_SOAP_ENC);
params.addElement(nameParam);
call.setParams(params);
Response resp = call.invoke(getURL(), SOAPActionURI);
//Check the response.
if (resp.generatedFault())
{
Fault fault = resp.getFault();
call.setFullTargetObjectURI(targetObjectURI);
throw new SOAPException(fault.getFaultCode(), fault.getFaultString());
}
else
{
Parameter refValue = resp.getReturnValue();
return ((javax.activation.DataHandler)refValue.getValue());
}
}
|
Next, we need to update the static initializer to change the two serializers (the serializer
and deserializer instances) to be instances of MimePartSerializer, and also change the mapping
of the XML type and class to be javax.activation.DataHandler:
{
org.apache.soap.encoding.soapenc.MimePartSerializer ser_1 =
new org.apache.soap.encoding.soapenc.MimePartSerializer();
org.apache.soap.encoding.soapenc.MimePartSerializer deSer_1 =
new org.apache.soap.encoding.soapenc.MimePartSerializer();
smr.mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
new QName("http://www.filetransferservice.com/schemas/
FileTransferServiceRemoteInterface",
"javax.activation.DataHandler"),
javax.activation.DataHandler.class, ser_1, deSer_1);
}
|
Now that you're done updating the client, you're almost ready to test the Web Service.
First, add a simple client. Create the following class in the package com.ibm.soap.attachments.clients:
package com.ibm.soap.attachments.clients;
import proxy.soap.com.ibm.soap.attachments.demo.*;
import javax.activation.*;
import java.io.*;
public class FileTransferClient {
public static boolean DEBUG = true;
public static void main(String[] args) {
try {
if (args.length != 2) {
System.out.print("Improper number of arguments.");
System.out.println("Syntax: java FileTransferclient inp_file out_file");
} else {
FileTransferServiceProxy proxy = new FileTransferServiceProxy();
DataHandler handler = proxy.getFileNamed(args[0]);
if (DEBUG)
printContents(handler);
else
writeContents(handler, args[1]);
}
} catch (Exception e) {
System.out.println("Exception e " + e);
}
}
public static void printContents(DataHandler handler) throws IOException{
InputStream strm = handler.getInputStream();
while (true) {
int c = strm.read();
if (c == -1)
break;
System.out.print((char) c);
}
}
public static void writeContents(DataHandler handler, String outputName)
throws IOException {
OutputStream out = new FileOutputStream(outputName);
handler.writeTo(out);
}
}
|
Before running this client you need to add the JavaMail JAR file to the Web project properties.
Classes in this file are used for base-64 serialization. You have to do this because the Web Services Wizard doesn't add the
mail.jar file (which is shipped with Apache SOAP, but is not used unless you're using SOAP with Attachments).
To do this, go to the Packages window, and from the Properties dialog, select
Java build Path in the left-hand menu. Then select the Libraries tab, select Add External Jar,
and add the following path (where <installroot> is the directory where you installed WebSphere Studio):
<installroot>/plugins/com.ibm.etools.server.jdk/jre/lib/ext/mail.jar |
You're now ready to test your server and client. First, switch to the Server perspective and start the default server (or the server with which the SOAPAttachmentsProject is associated). If you want to, you can also start the TCP/IP monitor to watch the SOAP messages flying over the wire. If you do so, update the URL in the Proxy class to use port 8081 instead of port 8080, or to add the following statement after the creation of the Proxy to your client:
proxy.setEndpointURL("http://localhost:8080/SOAPWithAttachmentsProject/
servlet/rpcrouter") |
Next, open a properties dialog on your new client class and set the input and output files (enter these in the Program Arguments entry field). If everything works, you should see something like this in the TCP/IP monitor (assuming that the file you're reading contains the text "This is it.":
Request:
POST /SOAPAttachmentsProject/servlet/rpcrouter HTTP/1.0 Host: localhost:8080 Content-Type: text/xml; charset=utf-8 Content-Length: 514 SOAPAction: "" <?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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getFileNamed xmlns:ns1= "http://tempuri.org/com.ibm.soap.attachments.demo.FileTransferService" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <name xsi:type="xsd:string">c://temp.txt</name> </ns1:getFileNamed> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Response:
HTTP/1.1 200 OK Server: WebSphere Application Server/4.0 Content-Type: multipart/related; boundary=633956732.1036640469055.JavaMail.kbrown.Melnibone; type="text/xml"; start="653371772.1036640469055.apache-soap.Melnibone" Set-Cookie: JSESSIONID=0000WQWSUHQVZ3B02KA3BJLAWUY:-1;Path=/ Cache-Control: no-cache="set-cookie,set-cookie2" Expires: Thu, 01 Dec 1994 16:00:00 GMT Content-Length: 1006 Content-Language: en Connection: close --633956732.1036640469055.JavaMail.kbrown.Melnibone Content-Type: text/xml; charset=utf-8 Content-Transfer-Encoding: 8bit Content-ID: <653371772.1036640469055.apache-soap.Melnibone> Content-Length: 552 <?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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getFileNamedResponse xmlns:ns1="http://tempuri.org/com.ibm.soap.attachments.demo.FileTransferService" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return href ="cid:622176636.1036640469055.apache-soap.Melnibone"/> </ns1:getFileNamedResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> --633956732.1036640469055.JavaMail.kbrown.Melnibone Content-Type: text/plain Content-Transfer-Encoding: 7bit Content-ID: <622176636.1036640469055.apache-soap.Melnibone> This is it. --633956732.1036640469055.JavaMail.kbrown.Melnibone- |
You've now seen some ways to make SOAP with Attachments work in Application Developer. It may seem like a lot of steps, but the procedure is not really that difficult, and along the way you've been able to see some of the "hidden corners" in the way Web Services are implemented in Application Developer. Hopefully this will be enough for you to start using SOAP with Attachments in your own projects.
Thanks to Maya Menon for prompting me to write this article with a question on JavaRanch, and for contributing to the sample client code.
- [Apache] Apache SOAP documentation
-
[Sun] Javadoc for JavaBeans Activation Framework (JAF)
- [W3C] SOAP Messages with Attachments
-
[Wizardry] IBM Redbook: Web Services Wizardry with WebSphere Studio Application Developer
Kyle Brown is a Senior Technical Staff Member with IBM Software Services for WebSphere. Kyle provides consulting services, education, and mentoring on object-oriented topics and Java 2 Enterprise Edition (J2EE) technologies to Fortune 500 clients. He is a co-author of Enterprise Java Programming with IBM WebSphere, the WebSphere AEs 4.0 Workbook for Enterprise Java Beans, 3rd Edition, and The Design Patterns Smalltalk Companion. He is also a frequent conference speaker on the topics of Enterprise Java, OO design, and design patterns. You can reach Kyle at brownkyl@us.ibm.com.




