Saturday, 23 October 2010

JAXB: Sharing object definitions between projects

This is about publishing java classes generated from xsd files using JAXB's xjc utility. Specifically I'm publishing a jar file from one project and using that jar in a second project. The second project also has an xsd file which imports the xsd from the first project. There are several tricks to this and I have put them into a sample here.

The sample would be two separate projects if it were in the real world, but it is easier to package it all into one. There is a first.xsd which is the xsd file from the first 'project'. You'll find a first-schema.xjb and a first-catalog.xml, and these are quite ordinary. The ant build script calls build-generated.xml to generate the java files and puts them into the generated1 directory. It then compiles those classes and packages them into a jar file along with the first.xsd file.

So, at that point we have a jar file called first.jar which contains the generated classes and an xsd file. This is what the first 'project' delivers. As I said, this is all very ordinary. If you're not too familiar with JAXB you might find them interesting but the second 'project' is where the good stuff is.

The second 'project' includes second.xsd, second-catalog.xml and second-schema.xjb. But before we get any further note that the first.jar file was placed into the temp/lib dir. The compile target (in build-generated.xml) has all the jars in temp/lib on its classpath. So first.jar is on the classpath for the second project compile.

Now, let's take a look at the second.xsd file. Note that it specifies its package name in the xsd like this:

<xsd:annotation>
  <xsd:appinfo>
    <jaxb:schemaBindings>
      <jaxb:package name="nz.co.senanque.jaxbsandbox2"/>
    </jaxb:schemaBindings>
  </xsd:appinfo>
</xsd:annotation> 

You will find a similar entry in first.xsd because it needs a package name as well.

Also in second.xsd you will find this:

<xsd:import namespace="http://www.example.org/sandbox" />

As well as a reference in the header:

xmlns:sandbox="http://www.example.org/sandbox"

So that when we refer to the Invoice object, which is defined in first.xsd, we can say sandbox:Invoice and the reference will be resolved... well almost. There is another step involving the catalog file. Catalog is not a specifically JAXB thing, though I've not had to use it on anything else yet. The second-catalog.xml file
has this entry:

<public 
  publicId="http://www.example.org/sandbox" 
  uri="jar:file:temp/lib/first.jar!/first.xsd"/>

This is almost obvious. The namespace we referred to in the second.xsd file (and remember we only used the namespace, nothing else) is mapped to a URI. The URI refers to the jar file we placed in temp/lib earlier. So that is enough information to find the first.xsd file and import it.

We are almost there. If you run the build in the samples you will find that the first.xsd classes are generated twice, ie in both the generated1 (where you would expect them) and in generated2 when we are really only trying to generate files for second.xsd. I've tried to suppress this second generation and you can see my commented out efforts in second-schema.xjb. Neither of these actually worked for me, but even if they had they would have involved naming each of the classes I want to suppress. This is okay on a sample project when there is only one, but not practical when there are hundreds of classes being imported, which is my real world scenario.

So I'm going to just live with that issue. It is not such a big issue anyway. My builds typically copy the generated classes off to another directory to compile it and I can easily just copy the one package I want and ignore the others.

Other notes:
  1. You have the option of specifying the package name in the xjb file rather than the xsd file. I prefer the xsd file because we can only specify one xjb file at a time, so my second-schema.xjb file would have to specify the package name for the first.xsd which is redundant information. It would, in the real world, have to specify about a dozen packages which is a maintenance issue.
  2. The xjb files for both first and second have a globalBindings option. This can also be specified in the xsd files. But if you do that, and you put it in both xsd files, xjc will complain about finding two globalBindings entries. So this does need to be in the xjc file.
  3. I'm using ant and ivy in the samples. The ivy depends on the ivyroundup repository. It should self-setup for you as long as you have ant and run the build.xml file (install ant, cd to the JAXBSandbox dir and type 'ant').
  4. You can put the schemaLocation on the import in the xsd file rather than use a catalog file. But then the exporting project is dictating where the importing project must store its xsd files. I prefer to keep them decoupled and catalog achieves this nicely.
Post a Comment