Using JAX-WS 2.0 with Castor (instead of JAXB)

In the Java EE 5 Web Services programming model, JAX-WS is tightly integrated with JAXB. To invoke a Web service with JAX-WS, you typically generate Java proxy classes from the Web service’s WSDL using a WSDL compiler tool such as the GlassFish wsimport utility. (For a quick intro to invoking Web Service’s using JAX-WS and the wsimport utility to compile WSDL, see: The Java EE 5 Tutorial). These proxy classes are largely compiled from the WSDL’s schema using the JAXB schema compiler and its XML Schema to Java mapping.

Sometimes, however, you might not want to use JAXB generated classes to invoke a Web service. You might want to use another binding tool like Castor, XML Beans, or JiBX. Replacing JAXB with a tool like Castor makes sense in situations like these:

You want to use your existing Java classes to invoke the Web service. For example, you might have a CreditCard class that you’d like to use, rather than dealing with the machine generated representation of a credit card produced by JAXB.

You can’t or don’t want to add JAXB annotations to your existing classes. If the classes are in production, you may not be able to modify them. Or it may require a lengthy process of modification, unit testing, system testing, etc. to get annotated versions of the classes put into production. In such situations, it may be easier and faster to work with a tool like Castor than JAXB.

You may already be using Castor, or another binding tool that you like, and want to continue using it along with JAX-WS.

If any of these situations describe you, you are in luck. JAX-WS is flexible enough to work with other Java/XML binding tools. Read on to see how it’s done.

This blog shows how you can use the Castor (www.castor.org) Java/XML mapping tool and the JAX-WS Dispatch interface to map existing Java classes to XML message that can be used to interact with Web services. These same techniques can be applied to work with other Java/XML mapping tools as well. I have chosen Castor for this example simply because it is a relatively popular open source tool.

The example used here is a simple purchasing Web service. This service has an operation named requestOrder that enables you to place an order for a list of items and get back an order confirmation. As you can see in the RequestOrder WSDL, the req:requestOrder element used to invoke the requestOrder operation, contains information such as the customer number (req:CUST_NO) and credit card (req:ccard). The req:ccard element has type oms:BUSOBJ_CCARD. If you were to use a WSDL compiler to generate Java proxy classes from this WSDL, the credit card element would be represented by a JAXB generated class compiled from the oms:BUSOBJ_CCARD type defined by the orders.xsd schema.

But, suppose that you want to use your existing credit card class, like the one below. This CreditCard class is a simple structure containing the properties of a credit card (e.g, card number, expiration date). In this example, we will use Castor to bind it to the credit card XML type - oms:BUSOBJ_CCARD - used by the Web service.

JAVA:
  1. public class CreditCard {
  2.   public String type;
  3.   public String num;
  4.   public String expireDate;
  5.   public String name;
  6.   public float amount;
  7.   public String chargeDate;
  8.  
  9.   public static class OrderCcard {
  10.     protected CreditCard ccard;
  11.   }
  12. }

Castor enables you to map existing Java classes to XML. Rather than using annotations, like JAXB, Castor uses an external mapping file. So, to map your classes to XML, you create a mapping file that associates each class and its properties with an XML type or element and its children.

This snippet from the Castor mapping file (the entire file is here), shows how the Java property expireDate gets mapped to the oms:CC_EXPIRE_DATE element in the oms:BUSOBJ_CCARD schema.

Once you have mapped all your classes to the corresponding WSDL schema types (e.g., credit card, items, etc.) you need a wrapper class that bundles these parameters together. This is the class that you will map directly to the contents of the SOAP message that invokes the Web service. In this example, the wrapper class is called MyRequestOrder:

JAVA:
  1. public class MyRequestOrder {
  2.   protected String custno;
  3.   protected String purchordno;
  4.   protected CreditCard ccard;
  5.   protected List itemList;
  6.  
  7.   public CreditCard getCcard() {
  8.     return ccard;
  9.   }
  10.  
  11.   public void setCcard(CreditCard ccard) {
  12.     this.ccard = ccard;
  13.   }
  14.  
  15.   // more setters and getters ...
  16.  
  17. }

You can see how the MyRequestOrder class is mapped to the SOAP request element (req:requestOrder) in the Castor mapping file here.

The code below shows how to invoke the Web service using these classes (i.e., CreditCard, MyRequestOrder, etc.) and the Castor mapping file. At the beginning of this code, the mapping file is loaded into an instance of org.exolab.castor.mapping.Mapping. The Castor Mapping class plays a role similar to the JAXBContext class in that it is used to configure marshallers and unmarshallers. Next, an instance of MyRequestOrder is created along with a ByteArrayOutputStream to marshal it into. The Castor org.exolab.castor.xml.Marshller instance is constructed as a wrapper around the ByteArrayOutputStream. Then, the Marshaller is configured by loading in the Mapping instance using the setMapping method.

JAVA:
  1. public static void main(String[] args) throws Exception {
  2.  
  3.   // load Castor Mapping File
  4.   FileInputStream castorMappingFile = new FileInputStream(args[0]);
  5.   Mapping castorMapping = new Mapping();
  6.   castorMapping.loadMapping(new InputSource(castorMappingFile));
  7.  
  8.   // Use Castor to marshal MyRequestOrder to XML
  9.   MyRequestOrder requestOrder = createRequestOrder();
  10.   Marshaller m = new Marshaller(new OutputStreamWriter(ba));
  11.   m.setMapping(castorMapping);
  12.   m.marshal(requestOrder);
  13.   Source xmlSource = new StreamSource(new StringReader(ba.toString()));
  14.  
  15.   // create Dispatch
  16.   URL wsdlURL = new URL("http://localhost:8080/oms/RequestOrderService?wsdl");
  17.   QName serviceQName =
  18.   new QName("http://www.example.com/req", "RequestOrderService");
  19.   Service service = Service.create(wsdlURL, serviceQName);
  20.   QName portQName =
  21.   new QName("http://www.example.com/req", "RequestOrderPort");
  22.   Dispatch dispatch = service.createDispatch(portQName,
  23.   Source.class, Service.Mode.PAYLOAD);
  24.  
  25.   // invoke web service with Castor generated XML
  26.   Source orderSource = dispatch.invoke(xmlSource);
  27.  
  28.   // ... process the result contained in orderSource …
  29.  
  30. }

The method invocation m.marshal(requestOrder);
marshals the MyRequestOrder instance to an XML document (stored in the ByteArrayOutputStream) according to the rules specified in the Castor mapping file that was loaded. From this point, the Dispatch instance is created an invoked using the XML document generated by Castor.

As you can see from this code snippet, it is the Dispatch interface that enables us to use Castor instead of JAXB as the Java/XML binding mechanism. Using Dispatch, you can send XML directly to the target Web service. That XML can be generated by Castor, XML Beans, JiBX, or whatever your favorite tool binding tool is.

This example is explained in a lot more depth (along with full source code and installation instructions to build and run it with GlassFish) in Chapter 6 of my book SOA Using Java EE 5 Web Services.

Leave a Reply

You must be logged in to post a comment.