JAXB generated classes of certain types implementing a custom interface
Asked Answered
G

2

6

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.

Gabie answered 16/6, 2015 at 13:29 Comment(0)
G
4

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.

Gabie answered 18/6, 2015 at 14:40 Comment(0)
O
4

Author of here.

See this issue in XJC. In short, XJC for some reason does not allow custom/vendor customization elements in SCD bindings.
inheritance:implements is such customization element.

So no, this does not work due a problem in XJC.

I personally bind via schemaLocation and XPath but use a "virtual" schema location URI and rewrite it via catalogs.

SCD would have been a much better choice (you're absolutely right here) but it just does not work.

Update on virtual schema location and catalogs.

Here's an example of binding which customizes some complex type:

<jaxb:bindings 
    schemaLocation="http://schemas.opengis.net/wps/2.0/wpsCommon.xsd" 
    node="/xs:schema">
    <jaxb:bindings node="xs:element[@name='Data']/xs:complexType">
        <wildcard:lax/>
    </jaxb:bindings>
</jaxb:bindings>

It is bound via schemaLocation and XPath. The schemaLocation is an existing URL, but in the build it is rewritten via catalog into the resource inside a Maven artifact:

REWRITE_SYSTEM "http://schemas.opengis.net" "maven:org.jvnet.ogc:ogc-schemas:jar::!/ogc"

So basically http://schemas.opengis.net/wps/2.0/wpsCommon.xsd will be loaded from the ogc-schema.jar!/ogc/wps/2.0/wpsCommon.xsd.

Using you can refer to binding files inside Maven artifacts as well:

                    <binding>
                        <dependencyResource>
                            <groupId>${project.groupId}</groupId>
                            <artifactId>ows-v_2_0</artifactId>
                            <resource>ows-v_2_0.jsonix.xjb</resource>
                            <version>${project.version}</version>
                        </dependencyResource>
                    </binding>

So, in combination, it allows writing binding files once and reuse them across modules.

But this was all a huge pain to figure out. I did it for the ogc-schemas project which is now some 50 heavily interconnected schemas. I heavily suffer from XJC drawbacks and issues, but this is the best possible at the moment. I even thought about forking and patching XJC, but this is far of effort limits I can afford.

So I've ended up with a number of mindblogging workarounds which somehow do the job at the end of the day.

Orthoptic answered 16/6, 2015 at 19:4 Comment(5)
Thanks lexicore. What about combining scd and xjc:superInterface... Should that work? Any idea why this approach generates no error, but also does not seem to have any effect on the generated source?Gabie
... or is xjc:superInterface considered a custom/vendor customization just like inheritence:implements?Gabie
It is actually a vendor extension but its a "first class" vendor extension which is specifically known to XJC (see here) - unlike inheritance:implements. By the way, isn't xjc:superInterface for global bindings only? Would explain why it is ignored on the class.Orthoptic
I think the only thing which would work at the moment is binding to some "global" schemaLocation + XPath + catalogs.Orthoptic
All efforts to use this approach so far fail with this error: [ERROR] "file:/C:/dev/workspace/myapp/etc/form-schemas/common.xsd" is not a part of this compilation. Is this a mistake for "file:/C:/dev/workspace/myapp/etc/form-schemas/v0001/common.xsd"?Gabie
G
4

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.

Gabie answered 18/6, 2015 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.