Saturday, April 14, 2012

Binding a custom XmlJavaTypeAdapter to the JAXB XJC Code Model using JAXB Binding Customizations

Recently I have been working on an application that heavily utilizes XML schema and JAXB. Since I chose XML Schema as my typing framework I desired to use JAXB's XJC code generator to keep Java model classes in sync with changes I made to the XML model. While 90% of the XJC generated code was sufficient there was 10% that I wanted to customize so that the Java model classes functioned more seamlessly with my framework. XmlAdapter seemed to be a perfect fit for my needs but the problem was how do I generate a reference to my custom XmlAdapter right in the middle of the XJC generated object model?

While there are plenty of trivial examples of using an XmlAdapter to convert a non-mappable Java object to XML all the ones I read utilized a complete handcoded JAXB object model and not one generated from XJC. After much research and head banging I came across two methods of introducing a XmlAdapter or any other valid hand coded JAXB object into the XJC generate code model using  JAXB XJC customizations in an external binding file.


Complete Simple or Complex Type Replacement using jaxb:class ref="..."


The standard <jaxb:class> custom binding actually supports a ref attribute so that a Simple or Complex type can be directly mapped to an existing Java class. The class must be a valid JAXB annotated object or mapped to an XmlAdapter via an XmlJavaTypeAdapter annotation at the package or class level. This technique is actually used extensively inside JAXB episodes for multi schema separate compilation. This seems to be an intuitive means of mapping hand coded JAXB objects into XJC generated code but surprisingly enough there is little documentation on it. If one does a google search on "JAXB bind customization" the first link to the Oracle documentation site completely omits all references to the ref attribute. Below is an example of how I used the jaxb:class ref attribute to bind interfaces to the XJC object model:

XSD:

 <simpleType name="srcIdType">
  <restriction base="ID">
   <pattern value="s\d+" />
  </restriction>
 </simpleType>
 
 <simpleType name="srcIdRefType">
  <restriction base="IDREF">
   <pattern value="s\d+" />
  </restriction>
 </simpleType>

 <complexType name="sourceType">
  <attribute name="src" type="tns:srcIdType" use="required" />
 </complexType>
XJB:

 <jxb:bindings node="//xs:simpleType[@name='srcIdType']">
  <jxb:class ref="somepackage.SrcId" />
 </jxb:bindings>

 <jxb:bindings node="//xs:simpleType[@name='srcIdRefType']">
  <jxb:class ref="somepackage.SrcIdRef" />
 </jxb:bindings>
 
Custom Java:

@XmlJavaTypeAdapter(SrcIdAdapter.class)
public interface SrcId {
 
 String id();

}

@XmlJavaTypeAdapter(SrcIdRefAdapter.class)
public interface SrcIdRef {
 
 String id();

}

public class SourceId implements SrcId, SrcIdRef {
 public SourceId() {
 }

 public SourceId(String id) {
  this.id = id;
 }

 private String id;

 @Override
 public String id() {
  return id;
 }

 public static class SrcIdAdapter extends XmlAdapter<String, SrcId> {

  @Override
  public String marshal(SrcId id) throws Exception {
   if (id != null) {
    return id.id();
   }
   return null;
  }

  @Override
  public SrcId unmarshal(String id) throws Exception {
   return new SourceId(id);
  }

 }

 public static class SrcIdRefAdapter extends XmlAdapter<String, SrcIdRef> {

  @Override
  public String marshal(SrcIdRef id) throws Exception {
   if (id != null) {
    return id.id();
   }
   return null;
  }

  @Override
  public SrcIdRef unmarshal(String id) throws Exception {
   return new SourceId(id);
  }

 }

}



XJC Generated Java:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sourceType", namespace = "somenamespace")
public class Source {
...
@XmlAttribute(name = "src", required = true)
protected SrcId src;
    

Using the custom code:

Source s = new Source();
s.setSrcId(new SourceId("b0i0"));

Here is an example of binding a default java class to a simpletype and using a package level annotation to associate the XMLAdapter
XSD:

<simpleType name="bondURIType">
 <restriction base="anyURI" />
</simpleType>

<complexType name="executionConfigType" abstract="true">
      <attribute name="uri" type="tns:bondURIType" use="required" />
</complexType>
XJB:

<jxb:bindings node="//xs:simpleType[@name='bondURIType']">
 <jxb:class ref="java.net.URI" />
</jxb:bindings>
 
Custom Java:

//URIAdapter.java
public class URIAdapter extends XmlAdapter<String, URI> {

 @Override
 public String marshal(URI uri) throws Exception {
  if (uri != null) {
   return uri.toString();
  }
  return null;
 }

 @Override
 public URI unmarshal(String uri) throws Exception {
  return URI.create(uri);
 }

}

//package-info.java
@XmlJavaTypeAdapter(URIAdapter.class)
@XmlSchema(namespace = "somenamespace", xmlns = { @XmlNs(namespaceURI = "somenamespace", prefix = "myprefix") }, elementFormDefault = XmlNsForm.QUALIFIED)
package somepackage; //needs to match XJC package declaration, XJC -npa flag should be set to not generate a package-info.java file

import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;


import somepackage.URIAdapter;

XJC Generated Java:

 @XmlAttribute(name = "uri", required = true)
 protected URI uri;
    

Property Replacement using jaxb:property/jaxb:baseType name=".."


Another scenario to consider is when one would like to use XJC to generate JAXB objects from a schema but then use a XmlAdapter to map a non-compliant Java object to the XJC generated classes. For example, I wanted to bind a Java Map to a custom complexType I wrote. It turns out that if one wants to change the Java type of a property one can use the property/baseType combination. Again, this was not very intuitive and I didn't find many examples on the Internet of this use case.


XSD:

 <element name="Root">
 <complexType>
  <sequence>
   <element ref="tns:TestVariables" minOccurs="1" maxOccurs="1"/>
  </sequence>
 </complexType>
</element>
 
<element name="TestVariables">
 <complexType>
  <sequence>
   <element name="TestVariable" minOccurs="0" maxOccurs="unbounded">
    <complexType>
     <attribute name="name" type="string" />
     <attribute name="value" type="string" />
    </complexType>
   </element>
  </sequence>
 </complexType>
</element>

XJB:

<jxb:bindings node="//xs:element[@name='Root']//xs:element[@ref='tns:TestVariables']">
 <jxb:property>
  <jxb:baseType name="test.TestMap" />
 </jxb:property>
</jxb:bindings>
 
Custom Java:

@XmlJavaTypeAdapter(TestAdapter.class)
public class TestMap extends LinkedHashMap<String,String>{ 

}

public class TestAdapter extends XmlAdapter<TestVariables, TestMap> {
 @Override
 public TestMap unmarshal(TestVariables value) {
  TestMap map = new TestMap();
  for (TestVariable var : value.getTestVariable())
   map.put(var.name, var.value);
  return map;
 }

 @Override
 public TestVariables marshal(TestMap map) {
  TestVariables vars = new TestVariables();
  for (Map.Entry entry : map.entrySet()) {
   TestVariable var = new TestVariable();
   var.name = entry.getKey();
   var.value = entry.getValue();
   vars.getTestVariable().add(var);
  }
  return vars;
 }

}


XJC Generated Java:

@XmlRootElement(name = "Root", namespace = "urn:test")
public class Root {

    @XmlElement(name = "TestVariables", namespace = "urn:test", required = true, type = TestVariables.class)
    protected TestMap testVariables;
    

Using the custom code:

Root root = new Root();
TestMap map = new TestMap();
root.setTestVariables(map);
..
m.marshal(root, writer);

There it is, two ways of injecting your own custom JAXB objects into the XJC generated object model. Now one can enjoy the full benefit of using a XML schema first development approach utilizing XJC to rebuild corresponding Java objects all the while having the flexibility to drop down to hand crafted JAXB objects when appropriate.

6 comments:

  1. thanks a lot! i struggled a lot with trying to make xjc to generate map instead of list and this is exactly what i needed. not sure if there is a simpler solution for my particular case, but at least it's working :). thanks again!

    ReplyDelete
  2. I'm trying to copy your example, but I see an error when compiling this:
    somepackage/SourceId.java:7: illegal start of type
    public class SourceId implements {

    ^
    Is that a typo? Looks like the interface is missing...

    ReplyDelete
  3. it didn't come out right in my last post:
    public class SourceId implements <SrcId, SrcIdRef> {

    ReplyDelete
    Replies
    1. Yes, you are correct, it should have been

      public class SourceId implements SrcId, SrcIdRef{

      I corrected the post. Thanks!

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Dear Aaron,
    thank you for your time writing this blog. I studied your 3 examples carefully, got them up and running with NetBeans 8 and Java 7-JAXB and learned something out of it!
    However I also noticed behaviours that I think you might have overlooked:
    1) The 3rd example ("Property Replacement using jaxb:property/jaxb:baseType...") has 2 issues: a) the Adapter never gets called when annotated on class TestMap. Only when it is annotated at the actual field of Type TestMap is the Adapter called. b) Because of a) and because there apparently exists a default mapping for Maps in JAXB the marshalled XML is not schema-conform. As far as I can tell, the only way to have the adapter called is by annotating the field in the genrated code.
    2) The second example ("binding a default java class to a simpletype and using a package level annotation..." a) doesn't need an adapter. If xs:anyURI is bound to a java.net.URI then it will be marshalled/unmarshalled withour further ado. b) again, annotating the adapter in package-info.java results in the adapter NOT being called.

    Since I need JAXB customization for SOAP-Webservices-WSDLs it is crucial that the marshaled XML validates. Therefore I'll have to bite the bullet and annotate xjc-generated code :-( Bt I see no other way. If I am missing something, I'll be glad to learn. If on the other hand you are interested in my sample-Projects (NetBeans-Projects with javax.xml.validation.Schema ...) I'll be glad to send them to you.
    sincerely
    Chris457

    ReplyDelete