Thursday, April 14, 2011

JAXB/xjc Java Generation with DTD

It is probably safe to assume that the majority of people who use JAXB today use the xjc compiler to create Java classes from XML Schema files rather than from Document Type Definition (DTD) files. However, I had a colleague ask not too long about about generating of Java objects from DTD using JAXB and in this post I look briefly at doing just that.

The next screen snapshot demonstrates that I'm using JDK 1.7.0 build 134 and its associated JAXB 2.2.3 implementation's xjc for the examples in this post.


This is the version of JAXB included with Java 7 (JAXB was also delivered with Sun's Java SE 6 JVM). The next screen snapshot shows the xjc binding compiler's usage statement with the part about DTD support highlighted.


The last screen snapshot tells us that xjc can generate Java classes from DTD by specifying the -dtd option. However, it also warns us that this DTD support is "experimental" and "unsupported."

For my simple example, I'm going to use a well-known DTD: log4j.dtd.

It is very simple to use xjc with a DTD. The following command, for example, will generated Java classes based on the log4j.dtd:
xjc -dtd -d generatedsrc -p dustin.examples log4j.dtd

This examples places the generated Java classes in a subdirectory called generatedsrc and creates subdirectories dustin\examples so that these generated classes are part of the dustin.examples package. The next screen snapshot proves that this worked.


It is often preferable to include generation of Java classes as part of an Ant build rather than from the command-line. The JAXB reference implementation includes support for a custom Ant task called xjc. The following snippet of XML code demonstrates using this custom task to generate Java files from the same log4j.dtd as used before.

   <taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
      <classpath>
         <fileset dir="C:\Users\Dustin\Downloads\jaxb-ri-20110412\lib"
                  includes="jaxb-xjc.jar" />
      </classpath>
   </taskdef>

   <target name="generateLog4jJavaClasses"
           description="Generate Java classes from DTD with JAXB's xjc">
      <property name="ant.xjc.target.dir" value="antgenerateddir" />
      <mkdir dir="${ant.xjc.target.dir}" />
      <xjc destdir="${ant.xjc.target.dir}" package="dustin.examples"
           schema="log4j.dtd">
      </xjc>
   </target>

When I only use the JAXB/xjc included with the Java SE 7 distribution, the XJCTask class is not readily available to support the xjc Ant task. To use this, I need to download the reference implementation, which does provide the JAR with the XJCTask class. I like to match the same version of the JAXB RI as is included with the Java distribution when possible. That's JAXB 2.2.3 in this case (Java SE 7). The executable JAR JAXB2_20110412.jar unzips its contents into a new subdirectory (C:\Users\Dustin\Downloads\jaxb-ri-20110412 in this case). The XJCTask class needed for the Ant custom task is in the JAR file (jaxb-xjc.jar) and directory (C:\Users\Dustin\Downloads\jaxb-ri-20110412\lib) provided specified in the taskdef. However, the above XML does not include any reference to DTD type and the output from trying to build with it is shown next.


The WARNING message ["Are you trying to compile DTD? Support for DTD is experimental. You may enable it by using the -dtd option."] is pretty good at hinting at what is likely the problem (schema is DTD rather than XML schema that is expected by default). The xjc Ant task needs to be told that a DTD is being used (similar to passing -dtd to command-line as done earlier). This is added to the build.xml file so that the relevant portion now looks like that shown in the next code listing.

   <taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
      <classpath>
         <fileset dir="C:\Users\Dustin\Downloads\jaxb-ri-20110412\lib"
                  includes="jaxb-xjc.jar" />
      </classpath>
   </taskdef>

   <target name="generateLog4jJavaClasses"
           description="Generate Java classes from DTD with JAXB's xjc">
      <property name="ant.xjc.target.dir" value="antgenerateddir" />
      <mkdir dir="${ant.xjc.target.dir}" />
      <xjc destdir="${ant.xjc.target.dir}" package="dustin.examples"
           schema="log4j.dtd">
         <arg value="-dtd" />
      </xjc>
   </target>

This does the trick.



Conclusion

It isn't much more difficult to generate Java with JAXB from a DTD than it is from an XSD with the most significant difference being the addition of the simple -dtd parameter. Of course, there are other differences in the results because of the significantly less descriptive ability of the DTD when compared to XML Schema. However, if a DTD is what one has available, JAXB's xjc binding compiler can make use of it to generate compliant Java classes.

1 comment:

@DustinMarx said...

Blaise Doughan posted JAXB and DTD - Apache log4j Example a few days ago. This is similar, but a different perspective to my post here.