Our project uses XFire's WsGenTask Ant task to generate service interfaces, DTO classes and exceptions from WSDL file and the referenced XSD files. WsGenTask delegates the generation of DTO classes to JAXB's XJC compiler. However, these DTO classes generated by JAXB XJC's default are not particularly developer-friendly, in the following aspects:
- They have only one default constructor. This is quite inconvenient when you need to instantiate a DTO to hold some values:
- You need to declare a local variable, which may otherwise be unnecessary.
- You need to call the constructor to instantiate a new instance, then you need to invoke the setter methods for each property value you would like to it to hold.
- To re-use the code and get rid of the local variable declaration, you need to create a factory class with one or more overloading create methods for each DTO class, which essentially are value-taking constructors moved to a factory class.
- They do not override the hashCode(), equals(Object) and toString() methods of the Object class. DTOs are value objects and no identity. Two instances holding exactly the same data should be considered interchangeable. Thus, overriding hashCode() and equals(Object) is essential for DTO classes. Moreover, not overriding toString() implementation makes it inconvenient in unit testing:
- You cannot invoke assertEquals(Object, Object) directly. You have to rely on a static method to determine whether two objects are equal in values. And if the assertion fails, you need to invoke another static method to have a useful string representation of both the expected and actual objects.
- Default values are not honoured in the generated classes, that means you have to set the value explicitly even if you are using a default value in most cases.
- If you are not the owner of the schemas being used, the class and property names generated may not be following the Java's camel-case conventions.
- There is no setter method for collections. You need to invoke the getter method to retrieve the collection and invoke the addAll(Collection) method to add all elements of the prepared collection.
- If an element has a maxOccurs attribute value greater than one, the getter method generated is not using plural by default.
- Date, time and dateTime are generated as XMLGregorianCalendar, which requires constant conversion to and from the java.util.Date and java.util.Calendar values you use in your domain models.
Fortunately, XJC has an extension option that allows third-party extensions. A whole bunch of XJC plugins have been developed to iron out most of these isses in the open source community, and many of them are under the JAXB 2.0 Commons project hosted by java.net.
Many of the these plugins are very useful:
- The Value Constructor plugin generates a constructor that takes values for all properties besides the default no-argument constructor.
- If you have many properties in a DTO class, or some of the properties are optional, you can use the Fluent API plugin, which generates builder-styled methods, which essentially provides named-parameter constructor, which is not provided by Java.
- The jakarta-commons-lang plugin generates overriding hashCode(), equals(Object) and toString() methods using jakarta commons lang's HashCodeBuilder, EqualsBuilder and ToStringBuilder classes, which in turn uses reflection.
- The Default Value plugin honours the default values specified in the schemas.
- The CamelCase Always plugin generates class and property names following the camel-case convention.
- The Collection Setter Injection plugin generates setter methods for collections.
Unfortunately, at the time of this writing, XFire's WsGen does not pass parameters to JAXB's XJC, as tracked by this XFIRE-1038 JIRA. The way to get around it is to call the XJCTask Ant task after the WsGenTask call and overwrite all DTOs generated by WsGenTask.
Note that some of the plugins required JAXB 2.1 to work. If you intend to use JAXB 2.0 in runtime environment, it is fine: you can generate the classes using JAXB 2.1 with the target specified as "2.0" to avoid generating annotations introduced in JAXB 2.1.
To generate a plural form for collections, use a simple JAXB binding file with XJCTask.
If you would like to work with java.util.Date or java.util.Calendar instead of XMLGregorianCalendar, you can provide your own parseMethod and printMethod and specify them in your JAXB binding file used by XJCTask, as detailed by Sun's engineer Kohsuke's blog entry.
Follwing are extracted from the ant build file to illustrate how to generate developer-friendly DTO classes using JAXB:
<!-- This task will autogenerate the code from the XSD specification -->
<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask" >
<classpath>
<pathelement path="${jaxb1-impl-2.1.3.jar}:${jaxb-api-2.1.3.jar}:${jaxb-impl-2.1.3.jar}:${jaxb-xjc-2.1.3.jar}" />
<pathelement path="${jaxb2-commons-commons-lang-plugin.jar}"/>
<pathelement path="${jaxb2-commons-value-constructor.jar}"/>
<pathelement path="${jaxb2-commons-fluent-api.jar}"/>
<pathelement path="${jaxb2-commons-default-value-plugin.jar}"/>
<pathelement path="${jaxb2-commons-collection-setter-injector.jar}"/>
<pathelement path="${component.classpath}" />
</classpath>
</taskdef>
<!-- This task will autogenerate the code from the WSDL specification -->
<taskdef name="wsgen" classname="org.codehaus.xfire.gen.WsGenTask" >
<classpath>
<pathelement path="${component.classpath}" />
</classpath>
</taskdef>
<!-- Auto generate classes based on the XSD definitions using JAXB bindings -->
<target name="xjc_gen">
<mkdir dir="${component.autogen_src}"/>
<delete>
<fileset dir="${component.autogen_src}"
excludes="**/service/**/*.java"/>
</delete>
<xjc destdir="${component.autogen_src}" target="2.0" extension="true">
<arg value="-Xcommons-lang"/>
<arg value="-Xvalue-constructor"/>
<arg value="-Xfluent-api"/>
<arg value="-Xcollection-setter-injector"/>
<arg value="-Xdefault-value"/>
<schema dir="${component.home}/src/conf/wsdl" includes="*.xsd"/>
<binding file="${component.home}/src/conf/wsdl/simple.xjb"/>
</xjc>
</target>
<!-- Auto generate interfaces and classes based on the WSDL definitions using JAXB bindings -->
<target name="wsdl_gen">
<mkdir dir="${component.autogen_src}"/>
<wsgen outputDirectory="${component.autogen_src}"
wsdl="${component.home}/src/conf/wsdl/service-foo.wsdl"
package="com.blogspot.ozgwei.service.foo"
overwrite="true"
binding="jaxb"
externalBindings="${component.home}/src/conf/wsdl/simple.xjb"
/>
<wsgen outputDirectory="${component.autogen_src}"
wsdl="${component.home}/src/conf/wsdl/service-bar.wsdl"
package="com.blogspot.ozgwei.service.bar"
overwrite="true"
binding="jaxb"
externalBindings="${component.home}/src/conf/wsdl/simple.xjb"
/>
</target>
<!-- Build all user, autogenerated and test code -->
<target name="compile" depends="wsdl_gen, xjc_gen">
...
</target>
The next is the content of the simple.xjb file for JAXB Binding, which use java.util.Calendar:
<!--
This enables the simple binding mode in JAXB.
See http://weblogs.java.net/blog/kohsuke/archive/2006/03/simple_and_bett.html
-->
<jaxb:bindings jaxb:version="2.0" jaxb:extensionBindingPrefixes="xjc"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings>
<xjc:simple/>
<jaxb:javaType name="java.util.Calendar" xmlType="xs:date"
parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
printMethod="javax.xml.bind.DatatypeConverter.printDate"/>
<jaxb:javaType name="java.util.Calendar" xmlType="xs:time"
parseMethod="javax.xml.bind.DatatypeConverter.parseTime"
printMethod="javax.xml.bind.DatatypeConverter.printTime"/>
<jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
printMethod="javax.xml.bind.DatatypeConverter.printDateTime"/>
</jaxb:globalBindings>
</jaxb:bindings>
If you prefer to use java.util.Date instead of java.util.Calendar, define the following class and change the binding file accordingly:
package com.blogspot.ozgwei.jaxb
import java.util.Date;
import javax.xml.bind.DatatypeConverter;
public class DateConverter {
public static Date parseDate(String s) {
return DatatypeConverter.parseDate(s).getTime();
}
public static Date parseTime(String s) {
return DatatypeConverter.parseTime(s).getTime();
}
public static Date parseDateTime(String s) {
return DatatypeConverter.parseDateTime(s).getTime();
}
public static String printDate(Date dt) {
Calendar cal = new GregorianCalendar();
cal.setTime(dt);
return DatatypeConverter.printDate(cal);
}
public static String printTime(Date dt) {
Calendar cal = new GregorianCalendar();
cal.setTime(dt);
return DatatypeConverter.printTime(cal);
}
public static String printDateTime(Date dt) {
Calendar cal = new GregorianCalendar();
cal.setTime(dt);
return DatatypeConverter.printDateTime(cal);
}
}
The simple.xjb will be changed to:
<jaxb:bindings jaxb:version="2.0" jaxb:extensionBindingPrefixes="xjc"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings>
<xjc:simple/>
<jaxb:javaType name="java.util.Date" xmlType="xs:date"
parseMethod="com.blogspot.ozgwei.jaxb.DateConverter.parseDate"
printMethod="com.blogspot.ozgwei.jaxb.DateConverter.printDate"/>
<jaxb:javaType name="java.util.Date" xmlType="xs:time"
parseMethod="com.blogspot.ozgwei.jaxb.DateConverter.parseTime"
printMethod="com.blogspot.ozgwei.jaxb.DateConverter.printTime"/>
<jaxb:javaType name="java.util.Date" xmlType="xs:dateTime"
parseMethod="com.blogspot.ozgwei.jaxb.DateConverter.parseDateTime"
printMethod="com.blogspot.ozgwei.jaxb.DateConverter.printDateTime"/>
</jaxb:globalBindings>
</jaxb:bindings>