Tuesday, August 21, 2007

How to make a JAX-WS client reuse existing classes for data binding (2).

The JAXB 2.1 binding customization is described in the JAXB 2.1 specification, chapter "CUSTOMIZING XML SCHEMA TO JAVA REPRESENTATION BINDING". For our purpose we will use following binding declarations:
  • <class> customizes the binding of a schema component to a class. It's ref attribute if specified, is the name of the value class that is provided outside the schema compiler. This customization causes a schema compiler to refer to this external class, as opposed to generate a definition. It must include the complete package name.
  • <typesafeEnumClass> this declararion has the same means of ref attribute and can be used for mapping Enum's.
Using these binding declarations we can create a JAXB customization file that binds represenation of some XML element to provided classes. An example which maps two complex types and enumeration simple type to java classes in your.packagage follows:
<jxb:bindings version="1.0"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jxb:bindings schemaLocation="YourSchemaFile" node="/xs:schema">
<jxb:bindings node="//xs:complexType[@name='employerResult']">
<jxb:class ref="your.package.employerResult">
</jxb:class>
</jxb:bindings>
<jxb:bindings node="//xs:complexType[@name='phoneResult']">
<jxb:class ref="your.package.PhoneResult">
</jxb:class>
</jxb:bindings>
<jxb:bindings node="//xs:simpleType[@name='eStatus']">
<jxb:typesafeEnumClass ref="your.package.EStatus"/>
</jxb:bindings>
</jxb:bindings>
</jxb:bindings>
Note that the form of customization document slightly differs for inlined / imported XML schema into the WSDL. In our case the schema is imported, so we can directly use standard JAXB 2.1 customization file. See schema customization link for more information. I would recommend keeping "client" customization in an external file and leave the WSDL general and clean, but here you can see an example of inline binding declarations incorporated directly to the WSDL.

If you are using the reference implementation of JAX-WS 2.1 and underlying JAXB 2.1 you can make thinks even simpler by using Separate compilation in the JAXB RI 2.1. Based on this technique, you can let the XJC compiler to generate binding declarations for you. The counterpart is that the generated binding declaration uses SCD references instead the XPath, what makes the customization file non-portable to other JAXB toolkit. Lets say the data binding of your web service is defined in the a.xsd schema included in WSDL. You can run
xjc -episode a.episode a.xsd 
The XJC compiler generates the data binding classes and additionally creates the a.episode customization documents like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
<bindings scd="x-schema::tns" xmlns:tns="http://processed.schema.namespace/">
<schemaBindings map="false"/>
<bindings scd="~tns:logOnUser">
<class ref="processed.schema.namespace.LogOnUser"/>
</bindings>
<bindings scd="~tns:logOnUserResponse">
<class ref="processed.schema.namespace.LogOnUserResponse"/>
</bindings>
</bindings>
</bindings>
The document contains binding declaration that explicitly bind XML elements to classes generated by XJC. All what we need is to re-allocate the ref attribute of generated binding declaration to own class implementation we want to reuse. [Note: The main purpose of generated episode file during Separate compilation is to provide customization that makes XJC reuse generated classes for binding other schemas with same elements. In the case of our web service it serves as a template for creating customization of a.xml itself].
Updating the generated template is straightforward if our web service uses Document Bare style. If the web service uses Document Wrapped then the result object is wrapped in the response bean. It does not have much sense to provide even own implementation of wrapper beans in that case. We can let the JAXB generate the wrapper classes and use own classes only for representing actual parameters and result objects. In example it means we remove the binding declaration for LogOnUserResponse and let reuse only the implementation of LogOnUser that represents the result parameter.
The last think you may need is to remove the <schemaBindings map="false"/> declaration which might cause some errors to JAX-WS toolkit. The resulted file may look like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bindings version="2.1" xmlns="http://java.sun.com/xml/ns/jaxb">
<bindings scd="x-schema::tns" xmlns:tns="http://processed.schema.namespace/">
<bindings scd="~tns:logOnUser">
<class ref="your.package.LogOnUser"/>
</bindings>
</bindings>
</bindings>

2 comments:

Unknown said...

Thanks! I have been trying to do this for some time now and consistently had some problem with my episode file!

vamsee said...

Thanks for writing this. It worked for me. A real-world example like this should be included in Sun's jax-ws manual.