Wednesday, August 10, 2016

An introduction to working with JAXB

I am in the process of migrating a few modules that are dependent on Apache XMLBeans to JAXB. It has been an exciting and challenging few days. I thought of jotting down a few important things I came across for anyone who might find it useful in the future.

First of all, let us look at setting up the maven plugin for the JAXB code generation. As of the time of writing this post, I came across two maven plugins;
Ended up using the first one as I found the configuration to be quite straightforward.

Your maven project structure will be as follows;
Project Folder->src->main->xsd
This will hold all the XSD files from which you would want to generate the JAXB objects.

Project Folder->src->main->xjb
This will holder your “bindings.xml” file, which is your data binding file used for any customization required as part of running the JAX generation task(xjc).

The plugin configuration for maven will be as follows;
 
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
     <version>2.2</version>
    <executions>
     <execution>
      <id>xjc</id>
      <goals>
       <goal>xjc</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <target>2.1</target>
     
     <sources>
      <source>src/main/xsd</source>
     </sources>
     
    </configuration>
  </plugin>


  • One thing that we were quite used with XMLBeans was the “isSet” type of methods for all optional elements which will check if the element is set or not. By default JAXB does not generate this method and you have to end up using the not null condition on each element. Thankfully, the binding configuration allows for this with the following;

<jxb:bindings 
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc"
    version="2.1">
<jxb:globalBindings  generateIsSetMethod="true"
</jxb:globalBindings>
</jxb:bindings>


  • By default, JAXB does not generate Java enumerations for the enumerations defined on the XSD files. The sad part is I could not find a way to apply this generation at a global level and could only handle it per XSD. But with XMLBeans, this was automatically done. In order to generate Java enumerations, the following should be done;
Sample XSD:

<xs:complexType name="EndpointType">
  <xs:attribute name="protocol">
   <xs:simpleType>
    <xs:restriction base="xs:string">
     <xs:enumeration value="HTTP"/>
     <xs:enumeration value="HTTPS"/>
     <xs:enumeration value="PAYLOAD"/>
    </xs:restriction>
   </xs:simpleType>
  </xs:attribute>
 </xs:complexType>


JAXB binding:
 
<jxb:bindings 
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc"
    version="2.1">
<jxb:bindings schemaLocation="../xsd/testconfig.xsd">
       
  <jxb:bindings node="//xs:complexType[@name='EndpointType']/xs:attribute[@name='protocol']/xs:simpleType">
               <jxb:typesafeEnumClass name="Protocol" />
        </jxb:bindings>
 
   </jxb:bindings>
</jxb:bindings>

schemaLocation – This is the relative path to the XSD I want to refer to. Since my “bindings.xml” resided in the “xjb” directory, I had to go one step up and go into the XSD directory to get the required XSD file.

node – Here you need to provide the xquery path of the simple type that has the enumeration defined. If you cross check this with the XSD provided, you will figure out how the XQuery path retrieves the given element.

Note: If in any event, your xpath returns multiple elements with the same name, you can still handle this by introducing the element multiple=”true” on the <jxb:bindings> element.
E.g : <jxb:bindings node="//xs:complexType[@name='EndpointType']/xs:attribute[@name='protocol']/xs:simpleType" multiple="true">


typesafeEnumClass – On this element you can provide the Java enumeration name to be generated.

  • XMLBeans by default converts all XSD date and date time elements to a Java Calendar object. With JAXB however, by default the XMLGregorianCalendar is used. Yet again the global bindings came to the rescue and this was handled with the below configuration which converted all XSD date elements to a Java Calendar object.


<jxb:bindings 
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc"
    version="2.1">

<jxb:globalBindings>

   <jxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
            parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
            printMethod="javax.xml.bind.DatatypeConverter.printDateTime"/>

        <jxb:javaType name="java.util.Calendar" xmlType="xs:date"
            parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
            printMethod="javax.xml.bind.DatatypeConverter.printDate"/>

        <jxb:javaType name="java.util.Calendar" xmlType="xs:time"
            parseMethod="javax.xml.bind.DatatypeConverter.parseTime"
            printMethod="javax.xml.bind.DatatypeConverter.printTime"/>
    </jxb:globalBindings>

</jxb:bindings>


  • If there is a need to make your JAXB objects serializable, this can be achieved with the following global binding configuration;

<jxb:bindings 
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc"
    version="2.1">

 <jxb:globalBindings >
 <xjc:serializable />
  
  </jxb:globalBindings>
 
 
</jxb:bindings>




The element that does the trick is the “<xjc:serializable/>” element.


  • With JDK 1.8, I faced an issue whereby if one of your XSD’s had an import for another schema to retrieve another XSD via HTTP, this was being blocked. An excerpt of the error thrown out was “because 'http' access is not allowed due to restriction set by the accessExternalDTD property”. The work-around in this case was to use the following maven plugin to set the VM arguments required to bypass this restriction. More information on this issue can be found here.

<plugin>
    <!-- We use this plugin to ensure that our usage of the
    maven-jaxb2-plugin is JDK 8 compatible in absence of a fix
    for https://java.net/jira/browse/MAVEN_JAXB2_PLUGIN-80. -->
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
   <version>1.0.0</version>
    <executions>
        <execution>
            <id>set-additional-system-properties</id>
            <goals>
                <goal>set-system-properties</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <properties>
            <property>
                <name>javax.xml.accessExternalSchema</name>
                <value>file,http</value>
            </property>
    <property>
                <name>javax.xml.accessExternalDTD</name>
                <value>file,http</value>
            </property>
        </properties>
    </configuration>
</plugin>


That is about it. I will keep updating this post as I go on. As always, your feedback on the same is always much appreciated.

Thank you for reading, and have a good day everyone.

No comments:

Post a Comment