Binding custom objects to attributes in JAXB

When generating Java sources from an XML Schema with JAXB, the type of field used to represent an attribute is determined by it’s type in the schema.

For example, a snippet from jabber-client.xsd:

<xs:element name="message">
  <xs:complextype>
    <xs:attribute name="from" type="xs:string" use="optional">
      <xs:attribute name="to" type="xs:string" use="optional">
    </xs:complextype>
  </xs:sequence>
</xs:element>

By default, JAXB would create the object (Message in this case) with two String properties. To change this to another object (say JID) we would add a simple binding changing these to use JID and define two static methods who’s job it is to convert between a String and a JID.

So the bindings would be something like:

<bindings schemalocation="jabber-client.xsd">
  <bindings node="/xsd:schema/xsd:element[@name='message']/xsd:complexType">
    <bindings node="xsd:attribute[@name='from']">
      <property>
        <baseType>
          <javaType name="uk.org.retep.xmpp.JID"
            parseMethod="uk.org.retep.xmpp.jaxb.adaptor.XMPPDatatypeConverter.parseJID"
            printMethod="uk.org.retep.xmpp.jaxb.adaptor.XMPPDatatypeConverter.printJID"
          />
        </baseType>
      </property>
    </bindings>
  </bindings>
</bindings>

Now for mose usecases this is fine and works pretty well. The problem is when you have a large number of custom bindings.

What JAXB does for each binding is that it generates an anonymous Adapter class. In that adaper class are two methods which call the two methods declared in the binding – in the case of binding to a Double you would get the following:

public class Adapter1
    extends XmlAdapter<String, Double>
{

    public Double unmarshal(String value) {
        return (javax.xml.bind.DatatypeConverter.parseDouble(value));
    }

    public String marshal(Double value) {
        if (value == null) {
            return null;
        }
        return (javax.xml.bind.DatatypeConverter.printDouble(value));
    }

}

The problem here is that JAXB generates one of these for every instance, so you can end up having multiple Adapter classes present, all doing the same thing. In one instance I had a single schema contain 16 duplicates of the above code. In retepXMPP I had almost 60 spread over multiple schemas just for handling JID. This is a lot of wasted, duplicated code and if you use a large number of schemas then this is going to cause both your jar sizes and permgen use to increase for no real reason.

So it would be nice to reduce this down to a single adapter.

Fortunately there is a way when using the JAXB RI. There is an alternate javaType binding which takes the full class name of an adapter class instead – using this causes JAXB to use that single class instead of generating these duplicate classes. The main difference is replacing the parseMethod and printMethod attributes with a single adapter attribute, and qualifying the javaType with the http://java.sun.com/xml/ns/jaxb/xjc namespace.

So the bindings would be something like:

<bindings
  xmlns="http://java.sun.com/xml/ns/jaxb"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  xmlns:retep="http://retep.org/xml/ns/retepTools"
  version="2.0">
  <bindings schemalocation="jabber-client.xsd">
    <bindings node="/xsd:schema/xsd:element[@name='message']/xsd:complexType">
      <bindings node="xsd:attribute[@name='from']">
        <property>
          <baseType>
            <xjc:javaType adapter="uk.org.retep.xmpp.jaxb.adaptor.JIDAdapter"
                          name="uk.org.retep.xmpp.JID" />
          </baseType>
        </property>
      </bindings>
    </bindings>
  </bindings>
</bindings>

Then just create a single adapter implementation. in retepXMPP thats some 60 classes reduced to one, if you generate any javadocs based on the generated sources there’s no more “Adapter” spam – large numbers of apparently duplicated classes in the javadocs etc.

Tip: Just make sure you keep an eye on that namespace – miss it out and XJC will spurt out some unusual error messages.

Update: I’ve disabled comments on this article simply because spammers are the only ones posting comments to it. Peter 27 Feb 2010

Advertisements