Back to Web Services Resources and Tutorials
For background material on this topic, first read Using the SOAP with Attachments API for Java to Generate a Client. The client developed here can be considered an extension of that client, so the introductory material there will not be repeated here.
The message-creation responsibilities of this client fall into three basic areas:
Creating the SOAP body -- root element
This part is relatively simple. We need to create the profile update request and attach the document to the SOAPBody of the SOAPMessage. To do this, we get the SOAPBody from the SOAPMessage (which we create with an instance of MessageFactory), then attach an XML document representing the "personalProfileUpdate" schema element, like the following example:
<swa:personalProfileUpdate xmlns:swa="http://example.com/swa/schema"> <profileID>wmadams824</profileID> <profilePhotoRef>C:/dev/SAAJ_Tutorial/furby.jpeg</profilePhotoRef> </swa:personalProfileUpdate>For this example, the user with ID "wmadams824" is requesting a profile update and is supplying an optional photo as part of the update.
The code to accomplish this is straightforward:
SOAPBody body = msg.getSOAPBody();
SOAPFactory soapFactory = SOAPFactory.newInstance();
Name bodyName = soapFactory.createName("personalProfileUpdate", "swa", "http://example.com/swa/schema");
SOAPBodyElement bodyElement = body.addBodyElement(bodyName);
// add the "profileID" element:
Name profileIDParamName = soapFactory.createName("profileID");
SOAPElement profileID = bodyElement.addChildElement(profileIDParamName);
profileID.addTextNode(args[1]);
// add the "profilePhotoRef" reference to the photo we will attach:
Name profilePhotoRefParamName = soapFactory.createName("profilePhotoRef");
SOAPElement profilePhotoRef = bodyElement.addChildElement(profilePhotoRefParamName);
profilePhotoRef.addTextNode(args[3]);
Where command-line arguments are used to populate the required elements.
One thing to note here is that the namespace of the body element (in this case,
"http://example.com/swa/schema") should be the namespace of the schema in which
the document was defined. In other words, if as is common you have one namespace
for your WSDL document and a different one for the schema defined in the WSDL
types section, the namespace you use here should be that of the schema.
If the namespace is missing or incorrect, you will get a service-invocation-time
error that says that the operation you are requesting (named "personalProfileUpdate")
is unrecognized.
Creating and attaching the personal profile
The only required element in the "personalProfile" document is the "realName" element. While for a production application it may be more convenient to have a Java-to-XML binding, for this simple example we'll just create the document by hand and attach it to the SOAPMessage. The document we'll be creating is
<swa:personalProfile xmlns:swa="http://example.com/swa/schema"> <realName>Wayne</realName> </swa:personalProfile>Note again the namespace declaration, as for the SOAPBody element.
To create the attachment, call the createAttachment() method on SOAPMessage, passing in a DataHandler. DataHandlers require content which matches the content type; for "text/xml", DataHandler expects a javax.xml.transform.Source object. A convenient way to create the XML content and instantiate the DataHandler is as follows:
String profileDetails = "<swa:personalProfile xmlns:swa=\"http://example.com/swa/schema\"><realName>" +
args[2] + "</realName></swa:personalProfile>";
StringReader reader = new StringReader(profileDetails);
StreamSource source = new StreamSource(reader);
DataHandler detailsDH = new DataHandler(source, "text/xml");
The next big question is how to identify the attachment so the web service can locate it. For optional or unsolicited attachments (the former, in the case of the photograph we'll be attaching), the location of the attachment is entirely under the control of your web service implementation. For identified message parts which are transmitted as MIME attachments, the generated/supplied framework will attempt to locate the attachment at service invocation time, and in the case of the Sun Application Server, a very specific format is required for the attachment content ID.
For example, if you set the content ID of the attachment as follows:
profileAttachPart.setContentId("profileDetails");
you will get a SOAPFault with the following string:
JAXRPCTIE01: caught exception while handling request: no attachment with id "profileDetails" found in messageeven though you can see from the attachment you've created:
------=_Part_0_15207001.1138210307347 Content-Type: text/xml Content-Id: profileDetails <swa:personalProfile xmlns:swa="http://example.com/swa/schema"><realName>Wayne</realName></swa:personalProfile>that the content ID is indeed "profileDetails". The reason is that the Sun Application Server framework generates, at web-service deployment time, code which iterates through the message attachments and looks for required attachments with a specific format (see Common Problems and Solutions). The expected format is:
<partName=UUID@domainName>All parts are optional except for the "partName", "=" and "@". So a Content-Id which would minimally satisfy the framework would be
profileDetails=@In our case, we'll generated a simple ID using the java.rmi.server.UID class and append our example domain:
profileAttachPart.setContentId("<profileDetails=" + (new java.rmi.server.UID()).toString() + "@example.com>");
Then, our resulting MIME attachment will look like:
------=_Part_0_15207001.1138211297534 Content-Type: text/xml Content-Id: <profileDetails=-25b5bdd2:10902ae18cf:-8000@example.com> <swa:personalProfile xmlns:swa="http://example.com/swa/schema"><realName>Wayne</realName></swa:personalProfile>and the service-side framework will conclude that it has found an attachment with Content-Id "profileDetails". Note that this behavior (on the service side) is consistent with the WS-I Attachments Profile 1.0 (see Resources), specifically requirement R2933.
Attaching the optional photograph
In our example, the optional photo is a JPEG file referenced on the client command line by filename. The example also outputs the SOAPMessage to stdout out before sending it to the web service. For this reason, there is a command-line option to base-64 encode the attachment (to avoid listening to the ASCII-7 characters). Note that you don't need to encode binary attachments in base64 unless you're using a transport which cannot handle raw binary (for example, SMTP servers). The Source used for the photograph is either a FileDataSource, for the unencoded version, or a java.lang.String for the encoded version.
// raw binary version:
FileDataSource binaryFds = new FileDataSource(args[3]);
DataHandler binaryFileHandler = new DataHandler(binaryFds);
binaryAttachPart = msg.createAttachmentPart(binaryFileHandler);
binaryAttachPart.setContentType("image/*");
...
// base64-encoded version:
String encodedFile = Base64.encode(fileBytes);
DataHandler base64Handler = new DataHandler(encodedFile, "text/plain");
binaryAttachPart = msg.createAttachmentPart(base64Handler);
binaryAttachPart.setMimeHeader("Content-Transfer-Encoding", "base64");
Note again that the "content type" must match that of the data being used as the
source. Also, note that setting the "Content-Transfer-Encoding" MIME header to
"base64" for the encoded version automatically results in 76-character line breaks
in the output (you get the attachment on a single line.
In the case of a base64-encoded image, you should see a MIME attachment similar to the following:
------=_Part_0_15207001.1138211297534 Content-Type: text/plain Content-Transfer-Encoding: base64 Content-Id: C:/dev/SAAJ_Tutorial/furby.jpeg LzlqLzRBQVFTa1pKUmdBQkFnQUFaQUJrQUFELzdBQVJSSFZqYTNrQUFRQUVBQUFBUEFBQS8rNEFE ... S0FDZ0FvQUtBQ2dBb0FLQUNnQW9BS0FDZ0FvQS8vMlE9PQ== ------=_Part_0_15207001.1138211297534--In this case we've set the "Content-Id" to the name of the file we are attaching; we have the freedom to set this value to whatever we want, as we will be locating the attachment ourselves on the service side. The WS-I Attachments Profile sets some interoperability requirements on the Content-Id (as mentioned above), but this requirement is specifically only for attachments which have been bound to WSDL message parts. While we could follow the same convention here, all that is strictly required is that the Content-Id of the attachment match the value for the attachment's swaRef, which was supplied in the SOAP body element (that is, the profilePhotoRef element of the personalProfileUpdate document).
Sending the message and receiving the response
After you've created the SOAPMessage and added the MIME attachments, you can send it to the web service with the following code:
SOAPConnectionFactory soapConnFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConn = soapConnFactory.createConnection();
SOAPMessage responseMsg = soapConn.call(msg, args[0]);
System.out.println("\nGot the following SOAPMessage in response:\n");
responseMsg.writeTo(System.out);
In the example client, note that the following code is executed before sending
the message:
if (msg.saveRequired())
{
msg.saveChanges();
}
This call is required after attaching parts; if you call writeTo(), the
message is saved automatically before it is output. But, in general, you should
save the message before returning it to ensure attachments are available. From
the API documentation on the saveChanges() method:
All MIME headers in a message that is created for sending purposes are guaranteed to have valid values only after saveChanges has been called.You can test to see if a save is required before making the call, as in the above example.
The output you can expect to see from the server is just the updateConfirmation object, as in this example:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns0="http://example.com/swa/schema">
<env:Body>
<ns0:updateConfirmation>
<confirmationCode>wmadams824_confirm: 1138211297752</confirmationCode>
</ns0:updateConfirmation>
</env:Body>
</env:Envelope>
in which a simple update confirmation string has been returned.
If you use the sample Ant build file referenced in the service-side part of this tutorial and if you place the sample SAAJ client under the src/client tree (in the correct package directory), you should be able to run the SAAJ client with the Ant target "test-saaj-client". You will most likely need to modify the build file to pass valid arguments to the client.
Back to Top
Back to Web Services Resources and Tutorials