I\'m facing a marshalling/unmarshalling problem involving inheritance and polymorphism using MOXy\'s JAXB implementation and external metadata bindings file.
I have
Below are the answers to your questions. The answer to question 2, is also an answer to question 1.
1st question : I understand that this is normal, Jaxb needs some way to determine the type of MyContaioner.myObject attribute. The problem is that I have no access to the incoming XML files, so I cant add xsi:type fields to them. Is there a way to determine a class based on the presence of a specific attribute in it ? regardless of it's value. If the source xml contains a @attrFromC attribute, I know the object should be of type C. If it contains attrFromB, it's B.
You can leverage the ClassExtractor extension in EclipseLink JAXB (MOXy) for this use case:
MyClassExtractor
A ClassExtractor is some code that you can implement to help MOXy determine which class it should instanitate. You are passed a Record and you can ask for the presence of the attributes at the current element by XPath to determine which class should be instantiated.
package com.test.example;
import org.eclipse.persistence.descriptors.ClassExtractor;
import org.eclipse.persistence.sessions.*;
public class MyClassExtractor extends ClassExtractor{
@Override
public Class> extractClassFromRow(Record record, Session session) {
if(null != record.get("@attrFromB")) {
return B.class;
} else if(null != record.get("@attrFromC")) {
return C.class;
} else {
return A.class;
}
}
}
Metadata (oxm.xml)
You can configure the ClassExtractor using the @XmlClassExtractor annotation. You can also do this via the external metadata file. I have adapted the one included in your question to include this:
Demo
The following demo code unmarshals each of the XML documents from your question, and outputs the type being held by the myObject property:
package com.test.example;
import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
Map properties = new HashMap();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader aXml = new StringReader(" ");
MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
System.out.println(myContainerA.getMyObject().getClass());
StringReader bXml = new StringReader(" ");
MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
System.out.println(myContainerB.getMyObject().getClass());
StringReader cXml = new StringReader(" ");
MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
System.out.println(myContainerC.getMyObject().getClass());
}
}
Output
[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.B] as no Property was generated for it.
[EL Warning]: 2012-01-20 10:36:41.828--Ignoring attribute [name] on class [com.test.example.C] as no Property was generated for it.
class com.test.example.A
class com.test.example.B
class com.test.example.C
2nd question : The other problem is that I dont know if Jaxb is capable of overriding xml attribute names like it is expected inside the XML file (@nameA, @nameB and nameC all referring to A.name), is there a way to do it ?
You can leverage an XmlAdapter for this question. This approach can also be used to answer your first question:
AAdapter
package com.test.example;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AAdapter extends XmlAdapter {
@Override
public AdaptedA marshal(A a) throws Exception {
if(null == a) {
return null;
}
AdaptedA adaptedA = new AdaptedA();
if(a instanceof C) {
C c = (C) a;
adaptedA.nameC = c.getName();
adaptedA.attrFromC = c.getAttrFromC();
} else if(a instanceof B) {
B b = (B) a;
adaptedA.nameB = b.getName();
adaptedA.attrFromB = b.getAttrFromB();
} else if(a instanceof A) {
adaptedA.nameA = a.getName();
}
return adaptedA;
}
@Override
public A unmarshal(AdaptedA adaptedA) throws Exception {
if(null == adaptedA) {
return null;
}
if(null != adaptedA.attrFromC) {
C c = new C();
c.setName(adaptedA.nameC);
c.setAttrFromC(adaptedA.attrFromC);
return c;
} else if(null != adaptedA.attrFromB) {
B b = new B();
b.setName(adaptedA.nameB);
b.setAttrFromB(adaptedA.attrFromB);
return b;
}
A a = new A();
a.setName(adaptedA.nameA);
return a;
}
public static class AdaptedA {
@XmlAttribute public String nameA;
@XmlAttribute public String nameB;
@XmlAttribute public String nameC;
@XmlAttribute public String attrFromB;
@XmlAttribute public String attrFromC;
}
}
Metadata (oxm-2.xml)
Demo2
package com.test.example;
import java.io.StringReader;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo2 {
public static void main(String[] args) throws Exception {
Map properties = new HashMap();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "com/test/example/oxm-2.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyContainerClass.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringReader aXml = new StringReader(" ");
MyContainerClass myContainerA = (MyContainerClass) unmarshaller.unmarshal(aXml);
System.out.println(myContainerA.getMyObject().getClass());
marshaller.marshal(myContainerA, System.out);
StringReader bXml = new StringReader(" ");
MyContainerClass myContainerB = (MyContainerClass) unmarshaller.unmarshal(bXml);
System.out.println(myContainerB.getMyObject().getClass());
marshaller.marshal(myContainerB, System.out);
StringReader cXml = new StringReader(" ");
MyContainerClass myContainerC = (MyContainerClass) unmarshaller.unmarshal(cXml);
System.out.println(myContainerC.getMyObject().getClass());
marshaller.marshal(myContainerC, System.out);
}
}
Output
class com.test.example.A
class com.test.example.B
class com.test.example.C