Using xsd:anyType to Represent XML Documents in a Service Interface: Design Details

The strategy for using xsd:anyType as the document type when designing a document-centric interface and endpoint is illustrated by the document-oriented applications available under the bpcatalog project hosted at java.net. This application has a service endpoint designed to use xsd:anyType as the document type. The XML document is represented by a purchase order sent by the client.

The application consists of these major entities:

The WSDL File for the Service

The WSDL file describes the web service. Code Example 1 contains a  snippet from the WSDL for this service. The development style we chose was to start with WSDL and generate the necessary Java[TM] classes from it. So choosing the proper types for the message parts in the WSDL is very important, since these types will result in the types of the parameters in the generated Java interface.  Note that the request message is declared to be type="anyType", which results in the generated Java interface shown in Code Example 2 of the service, which maps the input parameter to the Java SOAPElement type. Also, note an application-defined service exception declared in the WSDL.

  <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="urn:AnyTypePurchaseOrderService">
      <complexType name="submitPO">
    <sequence>
      <element name="BusinessDocumentRequest" type="anyType" nillable="true"/>                                            
    </sequence>
      </complexType>
      <complexType name="submitPOResponse">
    <sequence>
     <element name="result" type="string" nillable="true"/>                                               
    </sequence>
      </complexType>
      <complexType name="InvalidPOException">
    <sequence>
      <element name="message" type="string" nillable="true"/>                   
    </sequence>
      </complexType>
      <element name="submitPO" type="tns:submitPO"/>
      <element name="submitPOResponse" type="tns:submitPOResponse"/>
      <element name="InvalidPOException" type="tns:InvalidPOException"/>
      </schema>
    </types>
    <message name="AnyTypePurchaseOrderServiceSEI_submitPO">
        <part name="parameters" element="ns1:submitPO"/>
    </message>
    <message name="AnyTypePurchaseOrderServiceSEI_submitPOResponse">
        <part name="result" element="ns1:submitPOResponse"/>
    </message>
    <message name="InvalidPOException">
        <part name="InvalidPOException" element="ns1:InvalidPOException"/>
    </message>
    <portType name="AnyTypePurchaseOrderServiceSEI">
        <operation name="submitPO">
            <input message="tns:AnyTypePurchaseOrderServiceSEI_submitPO"/>
            <output message="tns:AnyTypePurchaseOrderServiceSEI_submitPOResponse"/>
            <fault name="InvalidPOException" message="tns:InvalidPOException"/>
        </operation>
    </portType>

Code Example 1:  Snippet From the WSDL for the Service

The Service Endpoint Interface

Because we start with the WSDL, the service endpoint interface gets generated based on the information contained in the WSDL. The service endpoint interface has a submitPO() method that accepts a javax.xml.soap.SOAPElementobject. Also, the application defined exception InvalidPOException is generated from the WSDL.

public interface AnyTypePurchaseOrderServiceSEI extends Remote {
    public String submitPO(SOAPElement businessDocumentRequest) throws
        InvalidPOException,  RemoteException;
}
Code Example 2:  The Service Endpoint Interface

The Java Endpoint Implementation

For this application, we chose to use an Enterprise JavaBeans[TM] (EJB[TM]) component to implement the service interface. We could instead have used a Web component to implement the endpoint. Either choice would have worked. An important point to notice is that the purchase order XML is handled as a SOAPElement. The endpoint implementation class is shown in Code Example 3.

public class AnyTypePurchaseOrderServiceBean implements SessionBean {
   
    private SessionContext sc;
    private POXMLUtil xmlUtil;
   
    public AnyTypePurchaseOrderServiceBean() {}
   
    public String submitPO(SOAPElement request) throws InvalidPOException, RemoteException {
        String poID = null;
        SOAPElement reply = null;
        try {
            NodeList list = ((Element)request).getElementsByTagName("poId");
            for (int loop = 0; loop < list.getLength(); loop++) {
                Node node = list.item(loop);
                if (node != null) {
                    Node child = node.getFirstChild();
                    if ((child != null) && child.getNodeValue() != null){
                        poID = child.getNodeValue();
                    }
                }
            }                      
        } catch (Exception exe) {
            throw new EJBException("AnyTypePOService Having Problems:"+exe.getMessage(), exe);
        }
        //this is done just to illustrate throwing an application specific exception
        if(poID.equals("100"))
            throw new InvalidPOException("Invalid ID for the purchase order!!! " +
                    "For demo purposes, we throw " +
                    "an application defined exception for the ID value of 100.");
        return poID;
    }
   
    //life cycle methods
    ...
}
Code Example 3:  The Endpoint Implementation

The Client Code for Accessing the Service

Now that we have seen the service code, let's also briefly look at the client for this application. The client would use the WSDL file in Code Example 1 and generate a Java service interface as in Code Example 4, and this Java interface of the service would be used as in the client code in Code Example 5 to send the purchase order to the service. Note that the purchase order data in this client application is gathered as input from a web application which holds the data in a client-defined PurchaseOrder type as shown in Code Example 6, and this purchase order Java object is converted to a SOAPElement,  which is then sent to the service.

public interface AnyTypePurchaseOrderServiceSEI extends Remote {

    public String submitPO(SOAPElement businessDocumentRequest) throws
        com.sun.j2ee.blueprints.docoriented.client.anytypeposervice.InvalidPOException, RemoteException;
}

Code Example 4: Client-Generated Java Service Interface Used to Access the Web Service

public class AnyTypePOServiceBD {
   
    private ServiceLocator serviceLocator;
   
    public AnyTypePOServiceBD(){
        serviceLocator = new ServiceLocator();
    }
   
    public String submitPO(PurchaseOrder po) throws RequestHandlerException {
        try {
            //this flag is to generate a wrapper element BusinessDocumentRequest
            //as defined in the WSDL
            boolean wrapper = true;
            AnyTypePurchaseOrderServiceSEI port = (AnyTypePurchaseOrderServiceSEI)
            serviceLocator.getServicePort(JNDINames.ANY_TYPE_SERVICE_REF,
                    AnyTypePurchaseOrderServiceSEI.class);
            SOAPElement requestSOAPElem = po.toXMLSOAPElement(wrapper);                  
            return port.submitPO(requestSOAPElem);
        } catch(InvalidPOException ipe){
            ipe.printStackTrace(System.err);
            throw new RequestHandlerException("Request Handler Exception: Service Endpoint Application-Defined Exception "+ipe.getMessage(), ipe);
        } catch(RemoteException re){
            re.printStackTrace(System.err);
            throw new RuntimeException("The web service you are trying to access is not available. A possible reason could be that the service has not been deployed yet. "+ re.getMessage(), re);
        }
    }
}
Code Example 5: Client Code for Sending a Purchase Order XML Document to the anyType Service

The helper PurchaseOrder class generated by the client application is shown in Code Example 6. In the client application, a web page form is submitted and then the PurchaseOrder is populated with data. Then this class is used in Code Example 5 to get a  representation of the purchase order XML document to be submitted to the service.  Notice that the PurchaseOrder class has a method  toXMLSOAPElement(), which converts the Java purchase order. The key thing to notice is that the purchase order XML is wrapped inside of a parent element BusinessDocumentRequest which is defined in the WSDL for this service. So, when using anyTpe to define a service, clients must modify the XML document and wrap it in the parent element defined in the WSDL. This puts a bit of a burden on the clients that want to use service interface, which uses anyType to represent the documents being exchanged with the service.

public class PurchaseOrder {    

    private String poId;
    private Calendar createDate;
    private Address shipTo;
    private Address billTo;
    private LineItem[] items;   

    public PurchaseOrder() {}   

    public PurchaseOrder(String poId, Calendar createDate,
            Address shipTo, Address billTo, LineItem[] items) {

        this.poId = poId;
        this.shipTo = shipTo;
        this.createDate = createDate;
        this.billTo = billTo;
        this.items = items; 
    }

    ...

     public SOAPElement toXMLSOAPElement(boolean wrapper) {
        SOAPElement soapElem = null;
        try {
            //construct the DOM tree
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            docBuilderFactory.setNamespaceAware(true);
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            Document doc = docBuilder.newDocument();
            Element  poElem = doc.createElement("PurchaseOrder");
            if(wrapper){
                Element  docElem = doc.createElement("BusinessDocumentRequest");
                doc.appendChild(docElem);
                docElem.appendChild(poElem);
            } else{
                doc.appendChild(poElem);
            }
            Element  elem = doc.createElement("poId");
            elem.appendChild(doc.createTextNode(poId));
            poElem.appendChild(elem);
            elem = doc.createElement("createDate");
            elem.appendChild(doc.createTextNode((new SimpleDateFormat("MM-dd-yy")).format(createDate.getTime())));
            poElem.appendChild(elem);
            elem = doc.createElement("shipTo");
            poElem.appendChild(shipTo.toDOM(doc, elem));
            elem = doc.createElement("billTo");
            poElem.appendChild(billTo.toDOM(doc, elem));
            for(int i = 0; i < items.length; ++i){
                poElem.appendChild(items[i].toDOM(doc));
            }
           
            //create a SOAPElement
            SOAPElement parent = SOAPFactory.newInstance().createElement("dummy");
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.transform(new DOMSource(doc), new DOMResult(parent));
            soapElem = (SOAPElement) parent.getChildElements().next();
        } catch(TransformerException te) {
            te.printStackTrace(System.err);
            throw new RuntimeException(te.getMessage(), te);
        } catch(ParserConfigurationException pce) {
            pce.printStackTrace(System.err);
            throw new RuntimeException(pce.getMessage(), pce);
        } catch(SOAPException se) {
            se.printStackTrace(System.err);
            throw new RuntimeException(se.getMessage(), se);
        }
        return soapElem;
}

Code Example 6: Snippet of PurchaseOrder Java Class Showing Wrapping of the Purchase Order XML Document

The Deployment Descriptors for the Service Endpoint

A web service in J2EE[TM] is packaged into a deployment module such as a war or an ear file. In this application, we packaged it as an ear file. The portable packaging of a web service endpoint requires several deployment descriptors as well as the Java classes. Here are some snippets from the endpoint deployment descriptors:

<enterprise-beans>
    <session>
      <ejb-name>AnyTypePurchaseOrderServiceBean</ejb-name>
      <service-endpoint>com.sun.j2ee.blueprints.anytypeposervice.AnyTypePurchaseOrderServiceSEI</service-endpoint>
  <ejb-class>com.sun.j2ee.blueprints.anytypeposervice.AnyTypePurchaseOrderServiceBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
   </session>  
</enterprise-beans>
Code Example 7:  Snippet From ejb-jar.xml Deployment Descriptor

Note that the service endpoint interface is defined as the <service-endpoint> in ejb-jar.xml. The endpoint also needs a web services deployment descriptor file, webservices.xml, like this:

  <webservice-description>
    <webservice-description-name>AnyTypePurchaseOrderService</webservice-description-name>
    <wsdl-file>META-INF/wsdl/AnyTypePurchaseOrderService.wsdl</wsdl-file>
    <jaxrpc-mapping-file>META-INF/anytypepurchaseorderservice-mapping.xml</jaxrpc-mapping-file>
    <port-component>
      <description>port component description</description>
      <port-component-name>AnyTypePurchaseOrderService</port-component-name>
      <wsdl-port xmlns:PurchaseOrderns="urn:AnyTypePurchaseOrderService">PurchaseOrderns:AnyTypePurchaseOrderServiceSEIPort</wsdl-port>
      <service-endpoint-interface>
        com.sun.j2ee.blueprints.anytypeposervice.AnyTypePurchaseOrderServiceSEI
      </service-endpoint-interface>
      <service-impl-bean>
        <ejb-link>AnyTypePurchaseOrderServiceBean</ejb-link>
      </service-impl-bean>
    </port-component>
  </webservice-description>
Code Example 8:  Snippet From webservice.xml Deployment Descriptor

The web services deployment descriptor file contains information about the service, such as the names of the WSDL file and the JAX-RPC mapping file, and also the service endpoint interface and the service implementation class names.

© Sun Microsystems 2005. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.