54

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?

Radek Postołowicz
  • 3,636
  • 2
  • 25
  • 40
Alex Spurling
  • 47,884
  • 23
  • 63
  • 71
  • 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: https://jaxb2-commons.dev.java.net/interface-insertion/xjc-if-ins-src.jar – rochb Nov 13 '09 at 16:43
  • is the way to go. – rochb Nov 21 '09 at 14:44

7 Answers7

50

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).

Martin
  • 1,917
  • 20
  • 22
Jim Hurne
  • 6,575
  • 3
  • 41
  • 42
  • 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`? – pulkitsinghal Dec 28 '13 at 18:24
  • 1
    Found out that `cxf-xjc-plugin` can also take advantage of similar setup if its tweaked like this: https://gist.github.com/pulkitsinghal/8163296 – pulkitsinghal Dec 28 '13 at 19:44
  • 1
    Also, the correct XPATH for the given example should be //xs:element[@name='Person']/xs:complexType[1] – RoyB Dec 30 '14 at 13:57
  • jaxb-xjc.jar from maven gives `no main in METAINF` error. Where do we get jaxb-xjc.jar that executes using `java -jar` option ? – ulab May 17 '16 at 11:34
  • 1
    All links are dead :( – OrangeDog Sep 22 '16 at 10:55
  • Any idea how to do this [via wsimport](http://stackoverflow.com/q/39640430/476716)? – OrangeDog Sep 22 '16 at 16:37
  • Some links for highsource doc can now be found on https://github.com/highsource/jaxb2-basics/wiki, or https://github.com/highsource/maven-jaxb2-plugin/wiki – Guillaume Husta Feb 21 '19 at 09:40
  • 1
    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 contrary – Levijatanu Feb 25 '20 at 09:24
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.

Jacob van Lingen
  • 6,165
  • 3
  • 39
  • 68
Chris
  • 504
  • 4
  • 6
  • I guess this only works with compile-time weaving, otherwise marshallers complain about the extra `ajc$instance` properties. – OrangeDog Sep 22 '16 at 16:36
5

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>
Alex Spurling
  • 47,884
  • 23
  • 63
  • 71
  • this worked perfectly. Though, the versions are pretty old, and I couldn't get the same solution to work with the "latest" versions – Sean Jun 28 '16 at 16:15
3

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.

Malcolm Smith
  • 3,432
  • 19
  • 28
  • 3
    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. – Clint Eastwood Dec 17 '13 at 17:34
  • 1
    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. – Malcolm Smith Dec 18 '13 at 10:18
  • 1
    If JAXB generates more than two classes, adding a global binding section using 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. – Clint Eastwood Dec 18 '13 at 12:07
  • 2
    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? – Malcolm Smith Jan 07 '14 at 15:29
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
takrl
  • 6,098
  • 3
  • 55
  • 64
cfsh
  • 11
  • 1
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.
Thimmayya
  • 2,004
  • 2
  • 18
  • 19
-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/

skaffman
  • 381,978
  • 94
  • 789
  • 754