I've got this API using JAXB to conveniently use object models, generated from XML Schemas by the XJC (XML-to-Java) compiler, through named references. It abstracts the creation of JAXB contexts and finding ObjectFactory methods away by all sorts of background magic and reflection. The basic gist of it is that you'd always define one general schema, and then any number (may also be 0) of schemas "extending" that general one, each resulting in its own data model. The general schema carries the reusable definitions, the ones extending it use those to compose their own models.
I've now run into the situation where I'd like to reuse the general schema for more than one project. The general type definitions should remain the same across projects, and some code will be built against the abstract classes generated from those. So I'd need to first generate classes for some generic schema, then generate those extending and using them separately. I'm using Maven for my build process.
The problem I'm running into is resolving type definitions from that generic schema in the extending schemas.
Suppose my generic schema is named "general.xsd" and looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/general"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">
<!-- Element (will usually be root) -->
<xs:element name="transmission" type="gen:Transmission" />
<!-- Definition -->
<xs:complexType name="Transmission" abstract="true">
<xs:sequence>
<!-- Generic parts of a transmission would be in here... -->
</xs:sequence>
</xs:complexType>
</xs:schema>
Next to that there's a bindings file to do some naming customization and set the package name for the output:
<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
version="2.1">
<!-- Bindings for the general schema -->
<bindings schemaLocation="general.xsd" node="/xs:schema">
<schemaBindings>
<package name="com.foobar.models.general"/>
</schemaBindings>
<bindings node="//xs:complexType[@name='Transmission']">
<!-- Some customization of property names here... -->
</bindings>
</bindings>
I'd then have the next bit in the POM of that project to generate the Java classes:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb21-plugin</artifactId>
<version>0.8.0</version>
<executions>
<execution>
<id>xjc-generate</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaDirectory>${basedir}/src/main/resources/com/foobar/schemas</schemaDirectory>
<schemaLanguage>XMLSCHEMA</schemaLanguage>
<addCompileSourceRoot>true</addCompileSourceRoot>
<episode>true</episode>
<removeOldOutput>true</removeOldOutput>
</configuration>
</execution>
</executions>
</plugin>
As you can see, I'm using the JAXB2.1 Maven plugin. I've set the option to have an episode file generated for step-wise compilation. The option to remove previous output was for a bug workaround; all it does is make sure everything's cleaned up first so recompilation is forced.
So far so good. That project compiles without a hitch. It should be noted that apart from the generated Java classes, I also package the schemas into the resulting jar file. So those are available on the classpath! The sun-jaxb.episode
file is in the META-INF, as it should be.
Then I start on the project that uses schemas which will extend the above, by first importing it. One of the "subtypes" could look like this (I'll call it sub.xsd):
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foobar.com/sub"
xmlns:sub="http://www.foobar.com/sub"
xmlns:gen="http://www.foobar.com/general"
elementFormDefault="qualified" attributeFormDefault="qualified">
<xs:import namespace="http://www.foobar.com/general" />
<!-- Definition -->
<xs:complexType name="SubTransmission">
<xs:complexContent>
<xs:extension base="gen:Transmission">
<xs:sequence>
<!-- Additional elements placed here... -->
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Again, there's a bindings file:
<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
version="2.1">
<!-- Bindings for sub type -->
<bindings schemaLocation="sub.xsd" node="/xs:schema">
<schemaBindings>
<package name="com.foobar.models.sub"/>
</schemaBindings>
</bindings>
</bindings>
And here's the bit from the POM of this project that takes care of the XJC generation:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb21-plugin</artifactId>
<version>0.8.0</version>
<executions>
<execution>
<id>xjc-generate</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaDirectory>${basedir}/src/main/resources/com/foobar/schemas</schemaDirectory>
<schemaLanguage>XMLSCHEMA</schemaLanguage>
<addCompileSourceRoot>true</addCompileSourceRoot>
<episode>false</episode>
<catalog>${basedir}/src/main/resources/com/foobar/schemas/catalog.cat</catalog>
<episodes>
<episode>
<groupId>com.foobar</groupId>
<artifactId>foobar-general-models</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</episode>
</episodes>
<removeOldOutput>true</removeOldOutput>
</configuration>
</execution>
</executions>
</plugin>
Originally, all the schemas were in a single folder and I had the schemaLocation
attribute in the import set to general.xsd
, which worked fine. But now that things are separated across projects, I run into problems. The first issue was that the other schema could not be found. I've resolved this by taking the schemaLocation
attribute out of the <xs:import />
element, keep only the namespace
attribute and adding a catalog file (catalog.cat
) which you can see referenced in the above POM extract. Its contents are:
PUBLIC "http://www.foobar.com/general" "classpath:/com/foobar/schemas/general.xsd"
This seems to work, since I no longer get an error that states the schema cannot be found. But for some reason, resolving the actual type definitions from the imported schema continues to fail. Here's the exception:
Error while parsing schema(s).Location [ file:/C:/NetBeans_groups/Test/SubModelBundle/src/main/resources/com/foobar/schemas/sub.xsd{...,...}].
org.xml.sax.SAXParseException: src-resolve: Cannot resolve the name 'gen:Transmission' to a(n) 'type definition' component.
Here's what I tried so far:
- Use a catalog file. Partially successful, since the imported schema can now be found.
- Have the compilation for the general schema generate an episode file and use this for the compilation of the sub schema. Doesn't appear to make a difference, although this should only play a role once the type was resolved, so I don't think this is important yet.
- Use a different JAXP (note: not JAXB, JAXP) implementation. It did use a different one, because I could see that in the exception's stack trace, but the end result is the same.
- Use the
maven-jaxb22-plugin
instead of 21. No difference.
Looking around online, it seems people have been running into this issue since at least 2006 and it might be related to some Xerces resolver problems. I hope that this is not some bug that's been lurking around for 6 years without anyone caring to fix it. Does someone else have some suggestions? Maybe someone ran into the same problem and found a solution? The only workaround I can think of is to use 'svn:externals' to drag the general schema into the sub project and just regenerate the classes there, but it's dirty and will only work when you can connect to our svn repo.
Much thanks in advance for reading this long post. Do keep in mind that I've taken all of the above from existing projects and replaced some namespaces and other things for anonymity, so some typos are possible.