JAXB @XmlElementWrapper Plugin...
When generating XML schema from Java source code using the schemagen tool a common approach is to use the @XmlElementWrapper and @XmlElement annotations to handle schema generation for collections. However, when generating Java source from a schema using the xjc tool the resulting code is not created with these annotations. Instead, the generated Java source contains "injected" inner classes to accommodate the collection of elements contained within an element.
To solve this small - but to me rather annoying - problem I've created a simple JAXB compiler plugin doing a "rewrite" of the default code generated using the xjc tool. The "rewritten" code will be straight forward and simple, and will have the @XmlElementWrapper and @XmlElement annotations around the collection fields.
Below, I will be illustrating the problem further with a few simple examples, before moving on to presenting my solution to the problem.
The impatient coder can skip the yada-yada-yada... and jump directly to the section on implementing the XmlElementWrapper JAXB plugin.
The Problem Illustrated
For the purpose of illustrating the problem, I will be using two simple classes - the Account and Posting classes. The Account class has fields accountNr and postings, with the latter returning a list of postings (List<Posting>). The Posting class contains 3 fields date, text, and amount. The Posting and its layout is not important for the sake of illustrating the problem.
Let's start by looking at generating a simple schema based on the Account and Posting classes. Just add @XmlRoot, @XmlAccessorType, and @XmlElement annotations to the Account class giving us something like the code shown in example 1.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Account
{
protected BigInteger accountNr;
@XmlElement(name="posting")
protected List<Posting> postings = new ArrayList<Posting>();
...
When running the schemagen tool for the above example, we will get a schema allowing us to work with XML files having a structure like the one outlined in example 2. If we apply the reverse process of generating Java code - using the JAXB xjc compiler on the generated schema file - we get Java code that looks very much like what we wrote initially, so we have a symmetric behaviour.
<?xml version="1.0" encoding="UTF-8"?>
<tns:account xmlns:tns="http://ns.conspicio.dk/example/code2xml/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ns.conspicio.dk/example/code2xml/v1 ../build/schemas/code2xml-v1.xsd ">
<tns:accountNr>1234567890</tns:accountNr>
<tns:posting>
<tns:date>2009-04-12T12:00:00</tns:date>
<tns:text>Amazon.com</tns:text>
<tns:amount>34.50</tns:amount>
</tns:posting>
<tns:posting>
<tns:date>2009-04-22T12:00:00</tns:date>
<tns:text>Gadgets Inc.</tns:text>
<tns:amount>243.70</tns:amount>
</tns:posting>
</tns:account>
Now, often we want to wrap the collection of elements (postning) in a parent element, which for this example would be something like postnings. Using the JAXB annotations, this is a quite simple task as we just add a @XmlElementWrapper annotation to the field defining our collection of elements. Doing so gives us code like that outlined in example 3.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Account
{
protected BigInteger accountNr;
@XmlElementWrapper(name="postings")
@XmlElement(name="posting")
protected List<Posting> postings = new ArrayList<Posting>();
...
When applying the schemagen compiler to our code, we will get a schema that will allow each of our posting elements to be placed inside a postings element - just like we would expect. An example document is shown in example 4.
<?xml version="1.0" encoding="UTF-8"?>
<tns:account xmlns:tns="http://ns.conspicio.dk/example/code2xml/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ns.conspicio.dk/example/code2xml/v2 ../build/schemas/code2xml-v2.xsd ">
<tns:accountNr>1234567890</tns:accountNr>
<tns:postings>
<tns:posting>
<tns:date>2009-04-12T12:00:00</tns:date>
<tns:text>Amazon.com</tns:text>
<tns:amount>34.50</tns:amount>
</tns:posting>
...
</tns:postings>
</tns:account>
If we now try to apply the reverse process and generate Java code from the schema using the JAXB xjc tool, we end up with generated code that is not very much like the code we wrote in example 3. Instead of code like that in example 3, we get code with an extra "injected" inner class representing our collection (Account.Postings).
In most cases I would personally prefer that the xjc compiler would generate code like the one I wrote in example 3 so I got symmetric behaviour and more readable code to use. However, due to the generality of the XML schema it may not be obvious to the xjc compiler when this is what we want.
An outline of the Java code generated from the schema with the postings element wrapper is shown in example 5.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "account", propOrder = {"accountNr","postings"})
public class Account {
protected BigInteger accountNr;
protected Account.Postings postings;
...
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"posting"})
public static class Postings {
protected List<Posting> posting;
...
While the code in example 5 is perfectly valid for our purpose, the resulting Java classes causes us to use an extra level of indirection when accessing the Posting objects in the resulting "collection" class (Account.Postings). So, when having deserialised a XML document into the object model represented by the example code above, we would do the following to access the first posting of an account:
Account account;
...
account.getPostings().getPosting().get(0);
...
Personally, I would prefer to access the postings of the account using the more straight forward syntax, similar to the one I would have been using if the resulting java code was like the code written in example 2. Using that Java code I would have been accessing the posting objects using:
Account account;
...
account.getPostings().get(0);
...
I've done some reading to see if I could find a way to have the xjc tool generate code like the one in example 3 from a schema having a wrapper element around collections. There may be a way of doing that...but I've just not found an example showing me how accomplish this.
As always, if a Google search does not answer your question - you will have to code...
Implementing the XmlElementWrapper JAXB Plugin
In my persuit of solving the above, I found an article by Kohsuke Kawaguchi on developing a JAXB RI plugin with the title "Writing a plug-in for the JAXB RI is really easy". With the promise of it being easy - I could see no reason not solving the problem this way.
As described in the article by Kohsuke, the xjc compiler will process XSD files and generate the Java AST for the schema model in something called an "outline" (object model representation of the Java code). This represents the Java code that will eventually be generated by the xjc compiler. However, a plugin will be able to selectively modify the Java AST (outline) before the code is actually written as .java files and as such, my plugin could just modify the code to use the @XmlElementWrapper and @XmlElement annotations - just as if I had written the code myself.
With this information, the plugin that I set out to develop would need to do the following:
- Inspect the Java AST (outline) and identify collection class candidates (classes like the
Account.Postingsclass). - Replace the type of fields referencing these classes with my own collection type (declarations like
Account.Postings postings;) - Add the
@XmlElementWrapperand@XmlElementannotations to those fields. - Replace the type used in set/get methods (like
Account.Postings getPostings()) with my own collection type - Make sure that my collection type was assigned an initial value during initialisation or on first access (for example "lazy" creating the collection in the
getPostings()method, if it is null). - (Optionally) remove the classes no longer needed by the Java code to be generated (classes like the
Account.Postingsclass)
For step 1 above this meant traversing all the classes in the outline and identify exactly those that contained exactly one field representing a sequence of other elements. Obviously, if the Account.Postings contained other fields than the collection of Posting objects we could not replace it with just a simple collection class.
For step 2, we would have to modify type of the field. In practice this means removing the original field and replacing it with a new field with the same name, but having a different type.
Regarding step 3 above, it showed up to be quite easy to add annotations once having added the field in step 2.
For step 4 the approach is quite similar to that for step 2. We have to locate the original getter and setter methods, delete them, and replace them with new representations matching our purpose.
Step 5 was actually the most tricky part, as we had to "enter" code into the body of the getter method returning the collection. If we were writing such a "lazy" initialisation line in plain Java code it would read something like:
...
if (field == null) field = new ArrayList<Posting>();
return field;
...
However, in order to modify the outline to contain such a line, we would have to create objects representing the code. This could look something like:
...
JFieldRef lhs = JExpr.ref(fieldName);
Class collection = java.util.ArrayList.class;
List<JClass> narrow = ((JClass)candidate.getField().type()).getTypeParameters());
JInvocation rhs = JExpr._new(implementationClass.owner().ref(collection).narrow(narrow);
JExpression assign = JExpr.assign(lhs, rhs);
JExpression condition = JExpr.ref(fieldName).eq(JExpr._null());
method.body()._if(condition)._then().assign(lhs, rhs);
method.body()._return(JExpr.ref(fieldName));
...
...but, that's all just a curiosity of working with the model and outline produced by the xjc tool...
To make the plugin more flexible (...or actually to suit my own needs...) I added a number of options to the plugin to modify the behaviour outlined in the design steps 1-6 above.
First of all, we might not want the plugin to modify all the classes it identifies as being candidates for modification, so adding the option of selectively specifying a number of classes to modify or not to modify allows the user to control what classes actually be modified. Secondly, the actual collection class used for holding elements used in the examples above is the java.util.ArrayList class. This may work well in may situations, but generally it would be preferable to be able to specify a custom collection class.
Finally, a number of minor options have been added, for example to control if the candidate classes being replaced should be deleted from the outline.
Using the XmlElementWrapper Plugin (xew)
The XmlElementWrapper JAXB plugin is used together with the xjc from the command line or from an Ant build task compiling your schemas and code.
The following arguments are used when applying the XmlElementWrapper plugin:
| Option | Comment |
|---|---|
| -Xxew | Activate the XML Element Wrapper plugin |
| -include filename | Specify a filename with candidate classes to include in the compilation. |
| -exclude filename | Specify a filename with candidate classes to exclude from the compilation. |
| -summary filename | Specify a filename to contain summary information on the compilation |
| -collection FQCN | Specify the class name of the collection type to use. |
| -instantiate lazy|early | Specify when the collection class should be instantiated. |
| -delete | Delete candidate classes having been replaced during compilation. |
To use the XmlElementWrapper plugin from Ant you will need something like the following in your build file:
To define the xjc Ant task:
...
<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
<classpath>
<fileset dir="${lib}/jaxb" includes="*.jar" />
<fileset dir="lib" includes="xew.jar" />
</classpath>
</taskdef>
...
To invoke the xjc Ant task with the XmlElementWrapper plugin:
<xjc destdir="${src-generated}" package="dk.conspicio.example.xml2code.v2">
<arg value="-Xxew" />
<arg value="-summary ${build}/xew-summary.txt" />
<arg value="-instantiate lazy" />
<schema dir="${src-examples}/schemas/v2" includes="*.xsd" />
<binding dir="${src-examples}/schemas/v2" includes="*.xjb" />
</xjc>
Download
The XmlElementWrapper is available for download from the projects page.
Feel free to use and modify the source to suit your particular needs, subject to the license terms distributed with the source code.
If you have comments/suggestions please feel free to use the contact form to submit a mail to me.