In the tip "Using the <xsd:any/> element for custom serialization," (see Resources), I described how you can use the <xsd:any/> element for custom serialization, and how to map this element to a Java object of type javax.xml.soap.SOAPElement. The code example showed how a client can use this interface to manually parse a response that was returned from an existing Web service. In this tip, I focus on the server side. I describe how you can create a response message in a Web service implementation class using the SOAP API for Attachments in Java (SAAJ).
Revisiting the <xsd:any/> element
Let me briefly discuss again how the <xsd:any/> element can help with custom creation and parsing of SOAP messages. This element represents any arbitrary XML content in an XML Schema. In other words, you can think of it as a wildcard in your schema. A JAX-RPC engine cannot know what this content will look like at runtime and hence passes it to the server or client as an instance of one of the core SAAJ interfaces, javax.xml.soap.SOAPElement. You can leverage this behavior by replacing those elements in your schema that you want to process through SAAJ with <xsd:any/>. Typically, you would only do this replacement for the sake of generating the right kind of JAX-RPC helper code, and eventually keep the original version of the schema in your WSDL file.
JAX-RPC version 1.1 mandates that a compliant engine offer tooling that lets the user select whether or not type-mapping should take place. This results in a similar effect as when using the <xsd:any/> element, namely that the engine doesn't try to map XML content into Java content and vice versa, but rather uses the javax.xml.soap.SOAPElement interface.
The following description of how to build a SOAP response message with SAAJ applies equally for both.
The Service Endpoint Interface
In JAX-RPC, each Web service is represented by a Service Endpoint Interface (SEI). It basically maps a WSDL portType into Java types, letting you either consume a Web service through a local Java proxy class or provide a Web service by implementing the SEI.
You can easily detect if SAAJ is to be used for a Web service through any occurrence of the javax.xml.soap.SOAPElement in the SEI. If a method on the SEI uses this interface as an input parameter, it means that the incoming SOAP request message is not mapped into Java types. If a method returns an instance of javax.xml.soap.SOAPElement, it means that the response message is not mapped, respectively.
In this example, you can generate two different SEI's, one for the server side using javax.xml.soap.SOAPElement, and one for the client side using Java type mapping. I focus only on the server side here, but you can download the entire example, including the client, by clicking on the Code icon at the top or bottom of this tip.
Here is an extract from the WSDL file that is used to generate the Service Endpoint Interface:
Listing 1. WSDL extract using the <xsd:any/> element
<wsdl:types>
<schema elementFormDefault="qualified"
targetNamespace="http://any.webservices.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema">
<element name="getOrder">
<complexType>
<sequence>
<element name="searchCriteria" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</element>
<element name="getOrderResponse">
<complexType>
<sequence>
<xsd:any maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
|
Note that the response message contains only one element, namely the <xsd:any/>. Here is the SEI:
Listing 2. The Service Endpoint Interface
package com.ibm.webservices.any;
import javax.xml.soap.SOAPElement;
public interface OrderManager extends java.rmi.Remote {
public SOAPElement[] getOrder(java.lang.String searchCriteria) throws
java.rmi.RemoteException;
}
|
As you can see, the response is sent back as an array of javax.xml.soap.SOAPElement instances.
Before taking a closer look at the service implementation, I describe the structure of the SOAP message that you can create in that implementation. Remember that there is no way to derive the structure of the message from the WSDL extract that I listed above. This definition is contained in the original XML Schema, which you add to the WSDL file before generating the client code:
Listing 3. The complete XML Schema
<wsdl:types>
<schema elementFormDefault="qualified" targetNamespace=
"http://any.webservices.ibm.com" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="getOrder">
<complexType>
<sequence>
<element name="searchCriteria" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</element>
<complexType name="Order">
<sequence>
<element name="createDate" nillable="true" type="xsd:dateTime"/>
<element name="customer" nillable="true" type="xsd:string"/>
<element maxOccurs="unbounded" name="lineItems" nillable="true" type=
"impl:LineItem"/>
</sequence>
</complexType>
<complexType name="LineItem">
<sequence>
<element name="itemDesc" nillable="true" type="xsd:string"/>
<element name="itemNumber" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
<element name="getOrderResponse">
<complexType>
<sequence>
<element name="getOrderReturn" nillable="true" type="impl:Order"/>
</sequence>
</complexType>
</element>
</schema>
</wsdl:types>
|
And here is an example SOAP response message that follows the schema above:
Listing 4. A Sample SOAP message
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<getOrderResponse xmlns="http://any.webservices.ibm.com">
<getOrderReturn>
<createDate>2004-07-03T22:57:45.359Z</createDate>
<customer>Bill Smith</customer>
<lineItems>
<itemDesc>This is a line item</itemDesc>
<itemNumber>12345</itemNumber>
</lineItems>
</getOrderReturn>
</getOrderResponse>
</soapenv:Body>
</soapenv:Envelope>
|
This is the message that you create in the service implementation class.
The Web service implementation
Typically, the JAX-RPC tool that generates the SEI from the WSDL file also generates a skeleton for the actual service implementation. In the implementation, you need to create the javax.xml.soap.SOAPElement instances that the SEI defines. This leads you deeper into the realm of SAAJ programming.
Here I walk you through each step that is required to build the appropriate response elements. Instances of javax.xml.soap.SOAPElement are created through a factory of type javax.xml.soap.SOAPFactory, so you need to create the factory first. Morever, each element needs a name, which in SAAJ is represented by the javax.xml.soap.Name interface. Name instances are not created through the factory, but instead you need an instance of javax.xml.soap.SOAPEnvelope for this. This instance is again created through a factory, namely one of type javax.xml.soap.MessageFactory. To answer what you are probably asking: yes, you really do need three different factories before you can start creating the actual elements.
Listing 5. Creating SAAJ factory instances
... MessageFactory messageFactory = MessageFactory.newInstance(); SOAPMessage message = messageFactory.createMessage(); SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); SOAPFactory factory = SOAPFactory.newInstance(); ... |
Now you are ready to create the content of the response message. Note that much of this code is repetitive and you could easily write it more efficiently than shown here. I also do not insert any kind of error handling in this example, but omitting it would be inappropriate for any production level code:
Listing 6. Creating SAAJ elements
...
// use the factory to create a new element
// the Name is created via the SOAPEnvelope, and we must define a
// namespace for each element in the body (to be WS-I compliant)
SOAPElement getOrderReturn = factory.createElement(envelope.createName(
"getOrderReturn", "","http://any.webservices.ibm.com"));
// now start adding children and build the full response message
// hardcode the text content for the sakes of simplicity
SOAPElement createDate = getOrderReturn.addChildElement(envelope.createName(
"createDate", "", "http://any.webservices.ibm.com"));
createDate.addTextNode("2004-07-03T22:57:45.359Z");
SOAPElement customer =
getOrderReturn.addChildElement(envelope.createName(
"customer", "", "http://any.webservices.ibm.com"));
customer.addTextNode("Bill Smith");
SOAPElement lineItems =
getOrderReturn.addChildElement(envelope.createName(
"lineItems", "", "http://any.webservices.ibm.com"));
SOAPElement itemDesc =
lineItems.addChildElement(envelope.createName(
"itemDesc", "", "http://any.webservices.ibm.com"));
itemDesc.addTextNode("This is a line item");
SOAPElement itemNumber =
lineItems.addChildElement(envelope.createName(
"itemNumber", "", "http://any.webservices.ibm.com"));
itemNumber.addTextNode("12345");
...
|
Finally, you add the element named getOrderReturn to an array of javax.xml.soap.SOAPElement (remember that you defined the <xsd:any/> element with a maxOccurs attribute value of "unbounded"):
Listing 7. Building the return array
... SOAPElement[] elements = new SOAPElement[1]; elements[0] = getOrderReturn; return elements; ... |
Creating a response from an existing DOM document
In many cases, you want to create a service implementation that does not build the response message one element at a time, but rather takes advantage of an already existing XML document that might have been retrieved from an existing backend. In this case, you wouldn't want to parse that existing piece of XML, simply to construct it back into a new XML message.
The SAAJ 1.2 specification introduced a new method on the javax.xml.soap.SOAPBody interface called addDocument(). Moreover, as of SAAJ 1.2 (which is also included in J2EE version 1.4), several of the SAAJ interfaces inherit from the respective DOM interface. For example, javax.xml.soap.SOAPElement extends the org.w3c.dom.Element interface, letting you use regular DOM programming to build your message. Since there is no widespread support for SAAJ 1.2 in the market yet, I used SAAJ 1.1 interfaces above.
The SAAJ specification defines interfaces and classes that let you build a SOAP message from scratch. You can use this to avoid using any type mapping from XML to and from Java types as defined in JAX-RPC. The typical use case for this is in cases where custom (de-)serialization is needed (for example, if the XML Schema for the required message is not fully supported by your JAX-RPC engine), or when existing code already handles XML artifacts, making type mapping obsolete or at least redundant.
This tip showed an example for a server-side service implementation that builds its response message in an element-wise manner. SAAJ 1.2 introduces additional methods to make handling of existing XML parts easier.
| Name | Size | Download method |
|---|---|---|
| ws-saajcode.ear | HTTP |
Information about download methods
- Read the first tip, "Using the <xsd:any/> element for custom serialization," describing the use of the <xsd:any/> element (developerWorks, January 2004).
- Get hands on training on the use of SAAJ in the tutorial "SAAJ" (Sun Developer Network).
- Find a number of tips related to Web services programming on developerWorks.
- Get the WS-I Basic Profile from the Web Services Interoperability Organization Web site.
- Access Web services knowledge, tools, and skills with Speed-start Web services, which offers the latest Java-based software development tools and middleware from IBM (trial editions), plus online tutorials and articles, and an online technical forum.
- Browse for books on these and other technical topics.
- Want more? The developerWorks SOA and Web services zone hosts hundreds of informative articles and introductory, intermediate, and advanced tutorials on how to develop Web services applications.
Andre Tost works as a Solution Architect in the WebSphere Business Development group, where he helps IBM Strategic Alliance partners integrate their applications with WebSphere Studio. His special focus is on Web services technology throughout the WebSphere product family. Before his current assignment, he spent ten years in various development and architecture roles in IBM software development, most recently for the WebSphere Business Components product. Originally from Germany, he now lives and works in Rochester, Minnesota. In his spare time, he likes to spend time with his family and play and watch soccer whenever possible.