Reputation: 61
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!
Upvotes: 5
Views: 1834
Reputation: 21
If you are still stuck with Java7 in 2017, then there is an easy way to circumvent this. The problem is that when serialiazing with a XmlREFId, Jaxb can't cope with sub-classes. A solution is to use an adapter and return an new instance of the base class with the same id as the one being serialized (only the id will be used, so we don't need to bother with the other fields):
public class ModuleXmlAdapter extends XmlAdapter<Module, Module> {
@Override
public Module unmarshal(Module v) {
// there is no problem with deserializing - return as is
return v;
}
@Override
public Module marshal(Module v) {
if (v == null) {
return null;
}
// here is the trick:
return new Module(v.getId(), null);
}
}
Then, just use that adapter wherever needed:
public class Module {
@XmlIDREF
@XmlJavaTypeAdapter(ModuleXmlAdapter.class)
private Module other;
//...
}
Upvotes: 0
Reputation: 10151
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.
Upvotes: 2
Reputation: 548
This seems to be a known bug in JAXB. I had the same problem and resolved it by not using XmlIDRef, but rather using postDeserialize with a custom ID.
Here is a bug report:
http://java.net/jira/browse/JAXB-870
I couldn't use Kohányi Róbert's suggestion. Did that work for you?
Upvotes: 0