Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XmlID/XmlIDREF and inheritance in latest JAXB version (Java 7)

I am using @XmlID and @XmlIDREF tags to reference one object from another. It was working fine in Java 6 even with inherited classes. The example code I've created looks like this. Base class used tags:

@XmlRootElement
@XmlAccessorType(FIELD)
public class Module {
    Module() {}

    @XmlIDREF
    private Module other;

    @XmlID
    private String id;

    public Module(String id, Module other) {
        this.id = id;
        this.other = other;
    }
}

Inherited class:

@XmlRootElement
public class TheModule extends Module {
    TheModule() {}

    private String feature;

    public TheModule(String id, Module other, String feature) {
        super(id, other);
        this.feature = feature;
    }
}

Container for these classes:

@XmlRootElement
public class Script {
    Script() {}
    public Script(Collection<Module> modules) {
        this.modules = modules;
    }

    @XmlElementWrapper
    @XmlElementRef
    Collection<Module> modules = new ArrayList<Module>();
}

When running this example code:

public class JaxbTest {

    private Script createScript() {
        Module m1 = new Module("Module1", null);
        Module m2 = new TheModule("Module2", m1, "featured  module");
        Module m3 = new Module("Module3", m2);
        return new Script(Arrays.asList(m1, m2, m3));
    }

    private String marshal(Script script) throws Exception {
        JAXBContext context = JAXBContext.newInstance(Module.class, Script.class, TheModule.class);
        Writer writer = new StringWriter();
        context.createMarshaller().marshal(script, writer);
        return writer.toString();
    }

    private void runTest() throws Exception {
        Script script = createScript();

        System.out.println(marshal(script));
    }

    public static void main(String[] args) throws Exception  {
        new JaxbTest().runTest();
    }
}

I receive XML, in Java 6:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other>Module2</other>
      <id>Module3</id>
    </module>
  </modules>
</script>

Note that reference to the m2 (TheModule instance) is serialized as expected. But when the same code is running under Java 7 (Jaxb 2.2.4-1) I receive:

<script>
  <modules>
    <module>
      <id>Module1</id>
    </module>
    <theModule>
      <other>Module1</other>
      <id>Module2</id>
      <feature>featured module</feature>
    </theModule>
    <module>
      <other xsi:type="theModule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <other>Module1</other>
        <id>Module2</id>
        <feature>featured module</feature>
      </other>
      <id>Module3</id>
    </module>
  </modules>
</script>

So you can see that on latest JAXB @XmlIDREF for inherited module is not working!

like image 770
Konstantin Lastochkin Avatar asked Nov 05 '22 10:11

Konstantin Lastochkin


1 Answers

This answer is wrong. Instead of relying on it read the documentation for JAXBContext.newInstance(...), see this answer and the comments below.


I think you confuse JAXB with the following line.

JAXBContext.newInstance(Module.class, Script.class, TheModule.class);

You tell it that you want to serialize the XML types Script, Module and TheModule. JAXB will treat objects of this latter type in a special way, because you've already supplied its base class: it adds a discriminating attribute to it. This way these two types can be differentiated in the serialized XML.

Try supplying only Script and Module, the base class for all modules.

JAXBContext.newInstance(Module.class, Script.class);

In fact, you can leave out Module entirely. JAXB will infer types in the Script object you try to serialize from the context.

This behaviour by the way isn't exactly related to Java 6. It's related to the JAXB implementation in use (okay-okay, almost the same thing, I know). In my projects I use JAXB 2.2.4-1, which reproduces the issue at hand in Java 6 and 7 too.

Oh, and one more thing: instead of creating a StringWriter and marshalling objects into that, then sending its contents to System.out you can use the following to send formatted XML to stdout.

Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(script, System.out);

Maybe this can ease (a tiny little bit) further testing.

like image 64
Kohányi Róbert Avatar answered Nov 15 '22 11:11

Kohányi Róbert