Search code examples
jaxbjaxb2xjcscd

JAXB generated classes of certain types implementing a custom interface


I am working on an application that uses XJC to generate Java POJOs from XSDs. There are dozens of schemas, and that number will grow. The application also needs to be able to handle different versions of the same schema, which means that I will have multiple schemas defining common types. I am trying to customize the bindings so that certain core types implement a common interface. The Inheritance plugin of JAXB2 Basics seems to do what I need, but I can't seem to nail the right syntax.

Here is the relevant part of my schema:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:my="http://example.com/core"
           targetNamespace="http://example.com/core"
           xmlns:xml="http://www.w3.org/XML/1998/namespace">

    ...

    <xs:complexType name="addressType">
        <xs:sequence>
            <xs:element name="Address" type="xs:string"/>
            <xs:element name="City" type="xs:string"/>
            <xs:element name="Province" type="xs:string"/>
            <xs:element name="Country" type="xs:string"/>
            <xs:element name="County" type="xs:string" minOccurs="0"/>
            <xs:element name="PostalCode" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>

    ...

</xs:schema>

... and this is what my custom binding file looks like:

    <?xml version="1.0"?>
<jaxb:bindings 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance"
    xmlns:my="http://example.com/core"
    jaxb:extensionBindingPrefixes="inheritance"
    version="2.1">

    <jaxb:bindings scd="x-schema::my" xmlns:my="http://example.com/core">
        <jaxb:globalBindings localScoping="toplevel">
            <jaxb:serializable/>
            <xjc:simple/>
        </jaxb:globalBindings>
        <jaxb:bindings scd="/type::my:addressType">
            <inheritance:implements>com.mysite.validator.ValidatableAddress</inheritance:implements> 
            <!--<xjc:superInterface name="com.mysite.validator.ValidatableAddress"/>-->
        </jaxb:bindings>
    </jaxb:bindings>

</jaxb:bindings>

I'm using the scd approach, because in all the "traditional" binding examples that show how to use the inheritence plugin, schemaLocation is specified. I want to avoid having to specify schemaLocation because of our large (and growing) number of schemas. I don't want to have to change the binding file every time we add a new schema. So, scd seems like it will satisfy this requirement.

However when I run the build using the above binding, I get this:

  [xjc] [ERROR] cvc-elt.1: Cannot find the declaration of element 'inheritance:implements'.
  [xjc]   line 18 of file:/dev/workspace/my_app/etc/schemas/bindings-common.xml
  [xjc] failure in the XJC task. Use the Ant -verbose switch for more details
  [xjc] classLoader = java.net.URLClassLoader@ebcdbb
  [xjc] SharedSecrets.getJavaNetAccess()=java.net.URLClassLoader$7@14562c5

If I comment out the inheritance:implements line and uncomment the xjc:superInterface line, the error goes away and the build completes successfully, but my AddressType classes don't implement the ValidatableAddress type.

Can the inheritence plugin be used with scd? Can xjc:superInterface be limited to only certain elements?

Cheers.


Solution

  • Thanks to lexicore for the prompt and detailed answers. However that approach wasn't working for me, so I ended up with the following solution...

    Because I am using Ant to invoke XJC, I ended up leveraging Ant's <copy filtering="true"...> capabilities to dynamically generate the binding file.

    Here is my binding "template" file (bindings-common.xml):

    <?xml version="1.0"?>
    <jaxb:bindings 
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance"
        xmlns:my="http://http://example.com/core"
        jaxb:extensionBindingPrefixes="inheritance" 
        version="2.1">
        <jaxb:bindings>
            <jaxb:globalBindings localScoping="toplevel">
                <jaxb:serializable/>
                <xjc:simple/>
            </jaxb:globalBindings>
        </jaxb:bindings>
    
        <jaxb:bindings
            schemaLocation="@bindingSchema@" 
            node="/xs:schema">
    
            <jaxb:bindings node="//xs:complexType[@name='addressType']">
                <inheritance:implements>com.example.validator.ValidatableAddress</inheritance:implements>
            </jaxb:bindings>
        </jaxb:bindings>
    
    </jaxb:bindings>
    

    Note this line:

    <jaxb:bindings
                schemaLocation="@bindingSchema@" 
                node="/xs:schema">
    

    This variable will get populated by Ant for each of the schemas that I am processing:

    <property name="jaxb.binding.template" value="../etc/form-schemas/bindings-common.xml"/>
    <property name="jaxb.binding.file" value="${jaxb.src.dir}/bindings-common${schema.version}.xml"/>
    
    <echo message="Filtering ${jaxb.binding.file} using template ${jaxb.binding.template}"/>
    
    <copy file="${jaxb.binding.template}" 
          tofile="${jaxb.binding.file}"
          filtering="true">
    
        <filterset>
            <filter token="bindingSchema" value="../../etc/form-schemas/${schema.version}/common.xsd"/>
        </filterset>
    </copy>
    
    <xjc destdir="${jaxb.src.dir}" 
            extension="true" 
            schema="${schema.file}" 
            package="${package}" 
            binding="${jaxb.binding.file}">
    
        <arg value="-episode"/> 
        <arg value="${jaxb.src.dir}/common${schema.version}.episode"/> 
        <arg line="-Xinheritance"/>
    
            <!-- Plugins -->
            <classpath>
                <fileset dir="../build-libs/">
                    <!-- JAXB2 Basics library -->
                    <include name="jaxb2-basics-0.6.4.jar"/>
                    <!-- JAXB2 Basics library dependencies -->
                    <include name="jaxb2-basics-runtime-0.6.4.jar"/>
                    <include name="jaxb2-basics-tools-0.6.4.jar"/>
                    <include name="javaparser-1.0.8.jar"/>
                    <include name="commons-beanutils-*.jar"/>
                    <include name="commons-lang-*.jar"/>
                    <include name="commons-logging-*.jar"/>
                </fileset>
            </classpath>
        </xjc>
    </target>
    

    Hopefully this will help future generations of JAXB victims.