Generating a JAXB class that implements an interface
Asked Answered
G

7

57

I'm currently using JAXB to generate java classes in order to unmarshall XML. Now I would like to create a new schema very similar to the first and have the classes that are generated implement the same interface.

Say for example, I have two schema files which define XML with similar tags:

adult.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:element name="Person">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="Name" type="xs:string" />
      <xs:element name="Age" type="xs:integer" />
      <xs:element name="Job" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

kid.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:element name="Person">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="Name" type="xs:string" />
      <xs:element name="Age" type="xs:integer" />
      <xs:element name="School" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

Using JAXB and XJC I'd like to generate two class files:

public class Adult implements Person {
    ...
    public String getName() { ... }
    public int getAge() { ... }
    public String getJob { ... }
}

public class Kid implements Person {
    ...
    public String getName() { ... }
    public int getAge() { ... }
    public String getSchool { ... }
}

where the Person interface defines the getName() and getAge() methods.

I've looked at some of the documentation for mapping interfaces but this appears to only be for the situation when you already have java classes that you want to map to a DOM.

Also, I've tried to use this external plugin but it doesn't appear to work. Here is my xjb binding file:

<jxb:bindings version="1.0" 
  xmlns:jxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
  xmlns:ext="http://xml.w-wins.com/xjc-plugins/interfaces"
  jxb:extensionBindingPrefixes="xjc">

    <jxb:bindings schemaLocation="xsd/adult.xsd" node="xs:schema/xs:complexType[@name='Person']">
        <ext:interface>mypackage.Hello</ext:interface> 
    </jxb:bindings>

</jxb:bindings>

but this gives the following error:

$ java -cp "lib/activation.jar;lib/InterfacesXJCPlugin.jar;lib/jaxb1-impl.jar;lib/jaxb-api.jar;lib/jaxb-xjc.jar;lib/jsr173_1.0_api.jar" com.sun.tools.xjc.XJCFacade -p mypackage.myxml -extension -Xinterfaces xsd/adult.xsd -b binding.xjb
parsing a schema...
[ERROR] XPath evaluation of "xs:schema/xs:complexType[@name='Person']" results in empty target node
  line 8 of file:/C:/dev/jaxb/jaxb-ri/binding.xjb

Failed to parse a schema.

Is it possible to generate a class with JAXB that implements an interface?

Update

I've tried using the Interface Insertion plugin but for some reason can't get it to work. This is how I'm calling xjc yet it is as if the plugin jar is not getting picked up from the classpath:

$ java -cp "lib/xjc-if-ins.jar;lib/jaxb-xjc.jar" com.sun.tools.xjc.XJCFacade -p mypackage -extension -Xifins myschema.xsd -b binding.xjb

I get the error:

unrecognized parameter -Xifins

Any ideas?

Givens answered 13/8, 2009 at 13:32 Comment(2)
The Interface Insertion plugin can be used to make a class generated from an XSD element implements a certain interface. Here you want to generate a class based on its schema name - you will require another plugin to do that. I've successfully made the interface insertion work with maven. Let me know if you need the details. The sources of the interface insertion are here: jaxb2-commons.dev.java.net/interface-insertion/…Zephan
<xjc:superInterface> is the way to go.Zephan
G
57

Unfortunately, it looks like the interface-injection plugin mentioned in some of the other answers is no longer well-supported. In fact, I'm having trouble finding the JAR for download.

Thankfully, the JAXB2 Basics Plugins provides a similar mechanism for adding an interface to the generated JAXB stubs (see the Inheritance plugin).

The Inheritance plugin documentation has an example showing what the XML schema file might look like. However, since you cannot modify the schema, you can use an external bindings file instead:

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

    <jxb:bindings schemaLocation="xsd/adult.xsd">
      <jxb:bindings node="//xs:complexType[@name='Person']">
        <inheritance:implements>mypackage.Hello</inheritance:implements> 
      </jxb:bindings>
    </jxb:bindings>

</jxb:bindings>

The JAXB2 Basics Plugins documentation includes instructions for using the plugin with Ant and Maven. You can also use it straight from the command line, but the command is a bit messy (due to the number of jars you have to add to the classpath):

java -jar jaxb-xjc.jar 
     -classpath jaxb2-basics-0.5.3.jar,jaxb2-basics-runtime-0.5.3.jar,
                jaxb2-basics-tools-0.5.3.jar,commons-beanutils-0.5.3.jar,
                commons-lang.jar,commons-logging.jar
     -p mypackage.myxml -extension -Xinheritance xsd/adult.xsd -b binding.xjb

The JAXB2 Basics Plugins provides a number of other utilities which you might also find useful (such as autogeneration of equals, hashCode, and toString methods).

Grainy answered 13/2, 2011 at 12:6 Comment(7)
Your example takes advantage of an extension or core-capability of the maven-jaxb2-plugin ... is there a similar extension for cxf-xjc-plugin that will allow us to setup something like -Xinheritance?Eatmon
Found out that cxf-xjc-plugin can also take advantage of similar setup if its tweaked like this: gist.github.com/pulkitsinghal/8163296Eatmon
Also, the correct XPATH for the given example should be //xs:element[@name='Person']/xs:complexType[1]Bolivar
jaxb-xjc.jar from maven gives no main in METAINF error. Where do we get jaxb-xjc.jar that executes using java -jar option ?Horme
Any idea how to do this via wsimport?Sunstroke
Some links for highsource doc can now be found on github.com/highsource/jaxb2-basics/wiki, or github.com/highsource/maven-jaxb2-plugin/wikiBosco
I just lost 2 hours debugging this solution ... it is very important to declare node like this node="/xs:schema/xs:element[@name='Person']/xs:complexType". It will ignore inheritance tag in contraryMccully
B
8

It might be overkill for your situation, but I have done this using AspectJ (we were already using aspects on that project so we already had the dependency and the exposure).

You'd declare an aspect along the lines of:

public aspect MyAspect
{
    declare parents: 
        com.foo.generated.Adult
    implements com.foo.Person;

    declare parents: 
        com.foo.generated.Kid
    implements com.foo.Person;
}

Which will add the interface com.foo.Person to the classes com.foo.generated.Adult and com.foo.generated.Kid

Might be overkill for your purpose, but it worked for us.

Boarder answered 18/8, 2009 at 1:24 Comment(1)
I guess this only works with compile-time weaving, otherwise marshallers complain about the extra ajc$instance properties.Sunstroke
G
6

The answer that worked for me was Jim Hurne's example of using the JAXB2 Basics plugin. But the documentation he linked appears to be no longer available so for reference, this is how I configured the maven plugin:

        <plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <version>0.8.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    </execution>
            </executions>
            <configuration>
                <extension>true</extension>
                <args>
                    <arg>-Xinheritance</arg>
                </args>
                <bindingDirectory>src/main/resources/xjb</bindingDirectory>
                <bindingIncludes>
                    <include>**.xml</include> <!-- This Should reference the binding files you use to configure the inheritance -->
                </bindingIncludes>
                <schemaDirectory>src/main/resources/xsd</schemaDirectory>
                <generateDirectory>${project.build.directory}/generated-sources/jaxb</generateDirectory>
                <generatePackage>mypackage</generatePackage>
                <plugins>
                    <plugin>
                        <groupId>org.jvnet.jaxb2_commons</groupId>
                        <artifactId>jaxb2-basics</artifactId>
                        <version>0.5.3</version>
                    </plugin>
                </plugins>
            </configuration>
        </plugin>
Givens answered 18/10, 2012 at 13:5 Comment(1)
this worked perfectly. Though, the versions are pretty old, and I couldn't get the same solution to work with the "latest" versionsStonybroke
S
6

It is possible to achieve this for this simple case without using a 3rd Party plugin using the JAXB RI Vendor Extensions xjc:superInterface customization. It should be noted this approach will cause all generated classes to implement the interface.

Example bindings file:

<jxb:bindings version="1.0" 
  xmlns:jxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
  jxb:extensionBindingPrefixes="xjc">
    <jxb:bindings schemaLocation="adult.xsd" node="/xs:schema">
    <jxb:globalBindings>
        <xjc:superInterface name="com.example.model.Person"/>
    </jxb:globalBindings>
    </jxb:bindings>
</jxb:bindings>

You then just need to run xjc specifying the binding file and setting the -extension flag. Much quicker/simpler than bringing in JAXB2Basics!

I was initially sceptical that this would work as the documentation states:

The customization allows you to specify the fully qualified name of the Java interface that is to be used as the root interface of all the generated interfaces. This customization has no effect unless you are specifically generating interfaces with the globalBindings generateValueClass="false" switch.

But when I tried it out with a bindings similar to the one above (without specifying generateValueClass="false", and generating classes, not interfaces), it gave me the output I required - all my generated classes implemented the common interface.

Suspensor answered 26/10, 2012 at 13:3 Comment(4)
this is not what the person asked (and usually, what most of the people want) as, as you said, this would make ALL the classes implement the same interface which is, most of the times, useless.Pankey
No, this is exactly what OP wanted; two schemas that generate two classes both implementing the same interface. It also happened to be a requirement I had in a real world project, so some times isn't useless at all. It is a fairly poorly documented area of JAXB, I don't think putting an example of its usage out there is a problem.Suspensor
If JAXB generates more than two classes, adding a global binding section using <xjc:superInterface> will result in ALL of the classes implementing the Person interface, which doesn't seem to be reasonable for a domain consisting of more than two classes where not all the objects are persons.Pankey
Yes, you are right, all the generated classes will implement the interface, as I state in the second sentence of my answer. Sometimes this might be useful, is easy to achieve, and IS what OP asked. OK?Suspensor
R
1

In my case the command-line call via java -jar works:

java -jar $somepath/jaxb-xjc.jar -classpath $somepath/xjc-if-ins.jar my.xsd -d $destdir -b $bindingconfig -p $desiredpackage -extension -Xifins

However when executing the xjc ant-task the error remains. The error message given is irritating as the real reason in my case is a bad version number in a class file ant tries to load (see stacktrace below). This correct error message only appears when you add the following to ANT_OPTS: -Dcom.sun.tools.xjc.Options.findServices=true

[xjc] java.lang.UnsupportedClassVersionError: Bad version number in .class file
[xjc]     at java.lang.ClassLoader.defineClass1(Native Method)
[xjc]     at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
[xjc]     at org.apache.tools.ant.AntClassLoader.defineClassFromData(AntClassLoader.java:1134)
[xjc]     at org.apache.tools.ant.AntClassLoader.getClassFromStream(AntClassLoader.java:1320)
[xjc]     at org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1376)
[xjc]     at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1336)
[xjc]     at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1074)
[xjc]     at java.lang.ClassLoader.loadClass(ClassLoader.java:299)
[xjc]     at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
[xjc]     at com.sun.tools.xjc.Options.findServices(Options.java:936)
[xjc]     at com.sun.tools.xjc.Options.getAllPlugins(Options.java:336)
[xjc]     at com.sun.tools.xjc.Options.parseArgument(Options.java:632)
[xjc]     at com.sun.tools.xjc.Options.parseArguments(Options.java:742)
[xjc]     at com.sun.tools.xjc.XJC2Task._doXJC(XJC2Task.java:444)
[xjc]     at com.sun.tools.xjc.XJC2Task.doXJC(XJC2Task.java:434)
[xjc]     at com.sun.tools.xjc.XJC2Task.execute(XJC2Task.java:369)
[xjc]     at com.sun.istack.tools.ProtectedTask.execute(ProtectedTask.java:55)
[xjc]     at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:291)
[xjc]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[xjc]     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[xjc]     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[xjc]     at java.lang.reflect.Method.invoke(Method.java:585)
[xjc]     at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
[xjc]     at org.apache.tools.ant.Task.perform(Task.java:348)
[xjc]     at org.apache.tools.ant.Target.execute(Target.java:390)
[xjc]     at org.apache.tools.ant.Target.performTasks(Target.java:411)
[xjc]     at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1360)
[xjc]     at org.apache.tools.ant.Project.executeTarget(Project.java:1329)
[xjc]     at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
[xjc]     at org.apache.tools.ant.Project.executeTargets(Project.java:1212)
[xjc]     at org.apache.tools.ant.Main.runBuild(Main.java:801)
[xjc]     at org.apache.tools.ant.Main.startAnt(Main.java:218)
[xjc]     at org.apache.tools.ant.launch.Launcher.run(Launcher.java:280)
[xjc]     at org.apache.tools.ant.launch.Launcher.main(Launcher.java:109)
[xjc]
[xjc] failure in the XJC task. Use the Ant -verbose switch for more details
Roseannroseanna answered 18/8, 2010 at 12:41 Comment(0)
U
0

The documentation for the interface-insertion plugin suggests the the following

[ To call xjc with the Interface Insertion Plugin from the command line, you may write:

java -cp $JAXB_HOME/share/lib/xjc-if-ins.jar -extension -Xifins schema

]

I am guessing that you are calling the main method of the wrong class - com.sun.tools.xjc.XJCFacade. You should probably retry with the exact syntax.

Here is a link on another forum which discusses a similar issue. http://forums.java.net/jive/message.jspa?messageID=220686

  • I would have posted this as a comment, but I do not have enough points to comment.
Urochrome answered 18/8, 2009 at 1:11 Comment(0)
D
-3

An XJC plygin of some description is the answer to your problem, it's just a matter of finding one that works. The best source for them is here:

https://jaxb2-commons.dev.java.net/

In particular, this one:

https://jaxb2-commons.dev.java.net/interface-insertion/

Disease answered 13/8, 2009 at 13:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.