Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Patterns and Strategies for Building Document-Based Web Services

 
Using the xsd:any Element in WSDL

[Page 1] [Page 2] [Page 3] [Page 4] [Page 5] [Page 6]

In JAX-RPC if there is no standard Java mapping for an XML schema type, a message part with literal representation is considered and mapped as an XML document fragment. The XML to Java mapping uses the interface javax.xml.soap.SOAPElement to represent a literal message part in the Java mapping of a wsdl:operation element.

In XML schemas the <any> element allows the complex types to be extended with elements not specified by the schema. This element can also be useful when the contents of the complex type don’t actually need to be defined in the schema. For example, the element BusinessDocument can have any elements in the XML document representing this schema.

			<element name="BusinessDocumentRequest">
				<complextype>

					<sequence>
						<any maxoccurs="1"/>
					</sequence>
				</complextype>
			</element>

In JAX-RPC, when the xsd:any schema type element is used to represent element wildcards, the mapping of the complex type will get mapped to a JavaBean as usual. However, the presence of the <any> element with a maxOccurs=1 results in an additional property called _any, which maps to a javax.xml.soap.SOAPElement (if the maxOccurs is greater than 1, it maps to an array of javax.xml.soap.SOAPElement).

Example

The mapping of the <any> element can be leveraged to transport XML documents over the wire in a document-literal formatting. The example below describes this in further detail, and the build tree can be found in the <SOAPElementExample> directory. This example is similar to the document-literal formatting example described earlier only the WSDL in Code Sample 41 below now maps the request to a BusinessDocument element that describes a complex type with any content using the <any> element declaration.

Code Sample 41: WSDL Extract With the any Element

<schema xmlns="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
targetnamespace="http://www.examples.com/types">
			<element name="BusinessDocumentRequest">
				<complextype>
					<sequence>
						<any maxoccurs="1"/>
					</sequence>
				</complextype>
			</element>
			<element name="BusinessDocumentReply">
				<complextype>
					<sequence>
						<any maxoccurs="1"/>
					</sequence>
				</complextype>
			</element>
			<element name="BusinessDocumentFault">
				<complextype>
					<sequence>
						<any maxoccurs="1"/>
					</sequence>
				</complextype>
			</element>
		</schema>

This WSDL is used to generate the server side bindings and artifacts using the wscompile tool. The resulting endpoint interface is shown in Code Sample 42.

Code Sample 42: Generated Endpoint Interface

// This class was generated by the JAXRPC SI, do not edit.
// Contents subject to change without notice.
// JAX-RPC Standard Implementation (1.1_03, build R65)

package com.examples.soapelement;

public interface IPurchaseOrder extends java.rmi.Remote {
    public com.examples.soapelement.BusinessDocumentReply
	    acceptPO(com.examples.soapelement.BusinessDocumentRequest parameters) throws
            com.examples.soapelement.BusinessDocumentFault,  java.rmi.RemoteException;
}

The class of interest is the generated BusinessDocument class, which contains the mapping for the <any> element in the form of a SOAPElement. This can be used to directly access the XML payload.

Code Sample 43: Generated BusinessDocument JavaBean

// This class was generated by the JAXRPC SI, do not edit.
// Contents subject to change without notice.
// JAX-RPC Standard Implementation (1.1_03, build R65)

package com.examples.soapelement;


public class BusinessDocumentRequest {
    protected javax.xml.soap.SOAPElement _any;
    public BusinessDocumentRequest() {
    }
    
    public BusinessDocumentRequest(javax.xml.soap.SOAPElement _any) {
        this._any = _any;
    }
    public javax.xml.soap.SOAPElement get_any() {
        return _any;
    } 
    public void set_any(javax.xml.soap.SOAPElement _any) {
        this._any = _any;
    }
}

With the artifacts generated, the implementation class for the service can be written and the service deployed. The implementation class in this case, shown in Code Sample 44, is similar to the one in Code Sample 36 earlier and simply prints out the contents of the SOAPElement to the console. This way, the service has direct access to the XML fragment representing the payload.

Code Sample 44: Service Implementation

public class PurchaseOrderService implements IPurchaseOrder,ServiceLifecycle {
 public BusinessDocumentReply acceptPO(BusinessDocumentRequest request) 
                throws BusinessDocumentFault,  java.rmi.RemoteException{
      if (request == null) {
           throw new BusinessDocumentFault(createExceptionXML("Web service was 
                                             passed a null Purchase order"));
         }
     try{
          SOAPElement requestdata= request.get_any();
          // print the request to the console
          printNodeToConsole(requestdata);
          SOAPElement replydata = createSOAPMessage("/Response.xml");
          BusinessDocumentReply reply = new BusinessDocumentReply();
           reply.set_any(replydata);
          return reply;
     }catch(Exception e){
         throw new BusinessDocumentFault(createExceptionXML("Web 
		     service could not construct a reply :" + e));
     }
// other methods not shown
}

The client artifacts can also be generated from the WSDL and the client implementation developed to use them. This is similar to the client in Code Sample 38; it reads the contents of an XML file and sends it as the payload to the acceptPO operation. The generated SOAP request is also shown in Code Sample 46.

Code Sample 45: Service Client

public static void main(String[] args) throws Exception {
        String url = "http://localhost:9090/anysoapelement/jaxrpc";
        if (args.length == 1) {
            url = args[0];
        }
        POService_Impl serviceproxy = new POService_Impl();
        IPurchaseOrder_Stub stub = (IPurchaseOrder_Stub) 
		    (serviceproxy.getIPurchaseOrderPort());
        stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, url);
        BusinessDocumentReply reply=sendPurchaseOrder(stub);
		SOAPElement payload= reply.get_any();
        printNodeToConsole(payload);
    }

    /**
     * Sends a purchase order to the service
     *
     * @param stub
     * @throws java.rmi.RemoteException
     */
    private static BusinessDocumentReply 
	    sendPurchaseOrder(IPurchaseOrder_Stub stub) throws Exception{
        BusinessDocumentRequest request = new BusinessDocumentRequest();
        SOAPElement requestdata = createSOAPMessage("purchaseorder.xml");
        request.set_any(requestdata);
        BusinessDocumentReply reply=stub.acceptPO(request);
		return reply;
    }

Code Sample 46: SOAP Request

<?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://www.examples.com/types">
	<env:body>
		<ns0:businessdocumentrequest>
			<ns0:purchaseorderdocument xmlns:tns="http://www.examples.com/types" 

xsi:schemalocation="http://www.examples.com/types PurchaseOrder.xsd">
				<billto>
					<street>1 Main Street</street>
					<city>Beverly Hills</city>

					<state>CA</state>
					<zipcode>90210</zipcode>
				</billto>
				<createdate>2004-03-27T12:21:02.055-05:00</createdate>
				<poid>ABC-CO-19282</poid>
				<items>
					<itemname>Copier Paper</itemname>
					<price>10</price>
					<quantity>2</quantity>
				</items>
				<items>
					<itemname>Toner</itemname>
					<price>920</price>
					<quantity>1</quantity>
				</items>
				<shipto>
					<street>1 Main Street</street>
					<city>Beverly Hills</city>
					<state>CA</state>
					<zipcode>90210</zipcode>
				</shipto>

			</ns0:purchaseorderdocument>
		</ns0:businessdocumentrequest>
	</env:body>
</env:envelope>

Strategy – Polymorphic Processors

The xsd:any element can be used to create an endpoint that is capable of processing the same document differently, depending on the message (for example, submit, update or cancel on a purchase order) or accepting multiple documents that conform to different XML schemas. This can be done by either:

  • Using a value greater than one for maxOccurs in the WSDL. The endpoint can then parse the SOAPElement array as a part of its implementation.
  • Have multiple ports in the WSDL and the different actions is indicated in the URL. Each port has a different URL pattern: for example, http://localhost:8080/bidservice/process, or http://localhost:8080/bidservice/cancel, so the endpoint knows what it needs to do with the document in the payload.
  • Include a custom SOAP header that is marked with a mustUnderstand=1 and specifies the intent by an externally negotiated schema between the two parties.
Consequences

Pros
  • The mapping of the xsd:any has been standardized to map to SOAPElement with JAX-RPC 1.1.
  • xsd:any extends an existing element, with arbitrary content not defined in the schema. This means that even though an element is named in the WSDL (for example, BusinessDocumentRequest) and the business document passed appears inside these elements on the wire, the web service its client can still work with complete XML documents and maintain schema integrity without having to include document content as under these elements (this is not the case with the anyType strategy discussed later).
    For example, the XML document returned as a response still maintains its integrity with the schema definition and is shown below
<?xml version="1.0" encoding="UTF-8"?>
<tns:status xmlns:tns="http://www.examples.com/types"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	      xsi:schemalocation="http://www.examples.com/types PurchaseOrderStatus.xsd">
    <orderid>ABC1080334678775</orderid>
    <timestamp>Fri Mar 26 15:57:58 EST 2004</timestamp>
</tns:status>

Cons
  • Developers have to work with the SOAPElement object directly and perform the necessary XML level manipulations manually. This requires a good understanding of SAAJ API and can be error-prone.
Using the anyType in WSDL

In XML Schemas, the anyType represents an abstraction for the base type from which all simple and complex types are derived. An anyType type has no restriction or constraints on the data content and it is quite possible to use the anyType like other schema types. This anyType can be used to pass XML document fragments between a service client and implementation, and retrieve the XML content. In the service the JAX-RPC implementation maps this schema type to a javax.xml.soap.SOAPElement. The example below (found in <SOAPElement-AnyType> directory) demonstrates how the anyType mapping can be leveraged to pass XML document fragments when starting from a WSDL.

Example

The WSDL shown in Code Sample 47 now describes the elements used in the messages to the acceptPO operation with the anyType data type.

Code Sample 47: WSDL with an anyType

<definitions xmlns:tns="http://www.examples.com/wsdl/poservice"
     xmlns="http://schemas.xmlsoap.org/wsdl/"
	     xmlns:ns1="http://www.examples.com/types"
		     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			     xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
				     targetnamespace="http://www.examples.com/wsdl/poservice"
					     name="POService">

  <types>
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
		    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
				    targetnamespace="http://www.examples.com/types">
	    	<element name="BusinessDocumentRequest"  type="anyType"/>
	    	<element name="BusinessDocumentReply"   type="anyType"/>
	    	<element name="BusinessDocumentFault"   type="anyType"/>
        </schema>
    </types>
	
	<message name="IPurchaseOrder_acceptPO">
		<part name="parameters" element="ns1:BusinessDocumentRequest"/>
	</message>
	<message name="IPurchaseOrder_acceptPOResponse">
		<part name="result" element="ns1:BusinessDocumentReply"/>
	</message>
	<message name="POProcessingProblem">
		<part name="POProcessingProblem" element="ns1:BusinessDocumentFault"/>
	</message>
	<porttype name="IPurchaseOrder">

		<operation name="acceptPO">
			<input message="tns:IPurchaseOrder_acceptPO"/>
			<output message="tns:IPurchaseOrder_acceptPOResponse"/>
			<fault name="POProcessingProblem"
			    message="tns:POProcessingProblem"/>
		</operation>
	</porttype>
	<binding name="IPurchaseOrderBinding" type="tns:IPurchaseOrder">
		<soap:binding style="document"
		    transport="http://schemas.xmlsoap.org/soap/http"/>
		<operation name="acceptPO">

			<soap:operation/>
			<input>
				<soap:body use="literal"/>
			</input>
			<output>
				<soap:body use="literal"/>
			</output>
			<fault name="POProcessingProblem">
				<soap:fault name="POProcessingProblem" use="literal"/>

			</fault>
		</operation>
	</binding>
	<service name="POService">
		<port name="IPurchaseOrderPort" binding="tns:IPurchaseOrderBinding">
			<soap:address
			    location="http://127.0.0.1:8080/docliteralfromjava/jaxrpc"/>
		</port>
	</service>
</definitions> 

This is similar to the examples presented earlier for the <any> element, with the difference that the generated service interface does not contain any wrapper classes (like BusinessDocumentReply) since the anyType is an actual data type and the mapping is defined by JAX-RPC, the XML elements gets mapped into a corresponding SOAPElement type in Java.

Code Sample 48: Generated Endpoint Interface from an anyType

// This class was generated by the JAXRPC SI, do not edit.
// Contents subject to change without notice.
// JAX-RPC Standard Implementation (1.1_03, build R65)

package com.examples.soapelement;

public interface IPurchaseOrder extends java.rmi.Remote {
  public javax.xml.soap.SOAPElement acceptPO(javax.xml.soap.SOAPElement parameters) 
     throws  com.examples.soapelement.POProcessingProblem,  java.rmi.RemoteException;
} 

The client class also requires a minor modification from that in Code Sample 45. It now has to explicitly set the attribute using the appropriately-named mutator method. The generated SOAP request is not shown here since it is identical to the request in Code Sample 46.

Code Sample 49: Client Class

public static void main(String[] args) throws Exception {
        String url = "http://localhost:9090/anytypesoapelement/jaxrpc";
        if (args.length == 1) {
            url = args[0];
        }
        POService_Impl serviceproxy = new POService_Impl();
        IPurchaseOrder_Stub stub = (IPurchaseOrder_Stub) 
                                   (serviceproxy.getIPurchaseOrderPort());
        stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, url);
        SOAPElement reply=sendPurchaseOrder(stub);
		printNodeToConsole(reply);
    }

    /**
     * Sends a purchase order to the service
     */
    private static SOAPElement sendPurchaseOrder(IPurchaseOrder_Stub stub) 
     throws Exception{
        SOAPElement request = createSOAPMessage("BusinessDocumentRequest.xml");
        SOAPElement reply=stub.acceptPO(request);
	   return reply;
    }
// other methods

There is, however, a difference from the preceding example. The XML documents that the client and service now have to work with have to be wrapped in the element named by the WSDL. For example, the document that the client received is shown in Code Sample 50. This does not actually conform to the document schemas.

Code Sample 50: XML Document Received by the Client

<?xml version="1.0" encoding="UTF-8"?>
<tns:BusinessDocumentReply xmlns:tns="http://www.examples.com/types">
<tns:Status xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.examples.com/types PurchaseOrderStatus.xsd">
    <orderid>ABC1080334678775</orderid>
    <timestamp>Fri Mar 26 15:57:58 EST 2004</timestamp>
</tns:Status>
</tns:BusinessDocumentReply> 

Strategy – Wrap Business Document with Operation

When implementing a generic document-based service with this approach, a strategy that can be followed to transport the XML document over the wire involves wrap the document in an element that describes the action that the service needs to perform on the payload. For example, the XML payload could look like Code Sample 51, where the search element notifies the service that the payload includes the data it needs to perform a search operation on the XML payload. The WSDL also needs to reflect the use of the named actions (for example, using cancel, search) in the element names.

Code Sample 51: Element Wrapping the Payload Describes the Operation

<tns:search xmlns:m="urn:examples">
  <tns:PurchaseOrderDocument
     <!-XML data here?
 </tns:PurchaseOrderDocument
</tns:search>

Consequences

Pros
  • Allows the action and the payload to be passed together. This can be useful when creating a polymorphic processor that accepts multiple document types or fragments with the same actions (or verb). For example, a single service that performs a search action on a purchase order and an invoice, both of which conform to different schemas. In such cases, the schema that the service conforms to contains the search action as an element. The anyType allows the remaining XML to be treated as a fragment of that schema.
  • This approach is better suited to passing XML document fragments rather than entire XML documents
Cons
  • In order for this strategy to be effective and to maintain integrity of the data with the schema, the schemas being used need to reflect the actions.
  • The disadvantage of relying on the anyType data type is that the JAX-RPC specification does not define standard Java mapping for the xsd:anyType, so not all implementations will behave like the Java WSDP and map this to a SOAPElement. In fact, support for the xsd:anyType is optional for an implementation
  • Since the anyType actually defines the data type for a named element in the WSDL, the business document being passed in the SOAP body is located inside this element identified in the WSDL. For example, the PurchaseOrder is inside the BusinessDocumentRequest element. This means that the document being passed now must either
    • have its root element identified in the WSDL
    • be constructed appropriately or wrapped in the element on the fly

[Page 1] [Page 2] [Page 3] [Page 4] [Page 5] [Page 6]