Bye bye XMLGregorianCalendar (part 2)
Change generated java classes with an XJC plugin.
In the previous post on ‘Bye bye XMLGregorianCalendar‘ I wrote about how you can influence the (un)marshalling of xml to java objects by adding an XmlAdapter to the classpath and an XmlJavaTypeAdapter annotation to a field in the java class. This technique works fine when you are actually writing the java classes your self, but: We don’t do that very often. One of the main features of XML is that it can be validated against a schema, a contact which defines the valid options and structures with the XML. Most of the times, when we work with XML, there is a schema availabe, either as an standard defined for an industry / product or as part of the contract of a webservice. We can leverage the XSD (XML Schema Definition) to generate Java objects, which represent the elements in XML.
As a part of JAXB, the Java SDK provides ‘xjc’, a Binding Compiler also known as an Xml-to-Java-Compiler. When running the xjc command you need to specify the location of the schema and it will generate a set of appropriate java classes for you. By default, the XJC binding compiler strictly enforces the rules outlined in the Compatibility chapter of the JAXB Specification, which will result in you having fields of the type javax.xml.datatype.XMLGregorianCalendar in your classes.
We don’t want that: We want to use DateTime of JodaTime as the type for our date fields.
By using the “-extension” switch of the xjc command, you are able to influence the process of java generation by activating custom JAXB Vendor Extensions. There are common extensions publically available, but none of those helps with replacing the XMLGregorianCalendar by properly annotated DateTime fields. Therefor you should write your own extension as an xjc plugin.
Writing a custom XJC Plugin.
So we are going to implement our own JodatimeJaxbPlugin, which will change the type of all date fields to DateTime and add the proper XmlJavaTypeAdapter-annotation to those fields. Implementing such an plugin is quite easy, since you just have to extend the com.sun.tools.xjc.Plugin and implement 3 abstract methods.
- String getOptionName(), which specifies by which command line argument value this plugin will be triggered. The default conventions is to start your option name with a capital ‘X’ to mark it as custom. For the JodatimeJaxbPlugin we use: ‘XuseJodatime’
- String getUsage(), which should return a description of this add-on.
- boolean run(Outline outline, Options opt, ErrorHandler errorHandler). The XJC compiler will do it’s internal stuff and then invoke this method to allow the plugin to tweak some of the generated code. The generated code is provided as the ‘outline’ of the generated classes.
The generated code by the XJC compiler is being represented in a com.sun.codemodel.JCodeModel, which allows access to the outlines of generated classes and fields. Our implementation of the plugin iterates over all generated classes, finds the fields within those classes of the type XMLGregorianCalendar and replaces those fields by an appropriate DateTime variant. The JCodeModel API doesn’t allow us to change the type of a generated fields, so we need to remove the old fields and add them again with the proper type. In that process we do want to preserve all metadata (Annotations) of the original fields and of course the getters and setters for those fields should be replaced as well, since the return type and parameter type will changes.
In order make the plugin available to the xjc compiler it has to be on the classpath and it needs to use the ServiceLoader API to register the plugin. Adding a simple text file in META-INF/service-directory with the name ‘com.sun.tools.xjc.Plugin’ and writing the fully qualified class name in that file is sufficient to register the plugin.
Running xjb from maven with the plugin
In order to trigger the custom plugin when the XJC compiler is being run from maven, you need to pass along the options to enable the extension and add the plugin to the classpath. The following sample snippet shows how to configure the maven-jabx-plugin for a custom plugin.
<plugin> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> <version>0.8.1</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <configuration> <extension>true</extension> <schemaDirectory>src/main/resources/xsd</schemaDirectory> <args> <arg>-XuseJodatime</arg> </args> <plugins> <plugin> <groupId>nl.iprofs.util.ws.jaxb</groupId> <artifactId>jodatime-jabx-plugin</artifactId> <version>1.0.0</version> </plugin> </plugins> </configuration> <dependencies> <dependency> <groupId>nl.iprofs.util.ws.jaxb</groupId> <artifactId>jodatime-jabx-plugin</artifactId> <version>1.0.0</version> <scope>runtime</scope> </dependency> </dependencies> </plugin>