Reputation: 63134
I'm using JAXB to read and write XML. What I want is to use a base JAXB class for marshalling and an inherited JAXB class for unmarshalling. This is to allow a sender Java application to send XML to another receiver Java application. The sender and receiver will share a common JAXB library. I want the receiver to unmarshall the XML into a receiver specific JAXB class which extends the generic JAXB class.
Example:
This is the common JAXB class which is used by the sender.
@XmlRootElement(name="person")
public class Person {
public String name;
public int age;
}
This is the receiver specific JAXB class used when unmarshalling the XML. The receiver class has logic specific to the receiver application.
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff() ...
}
Marshalling works as expected. The problem is with unmarshalling, it still unmarshals to Person
despite the JAXBContext using the package name of the subclassed ReceiverPerson
.
JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
What I want is to unmarshall to ReceiverPerson
. The only way I've been able to do this is to remove @XmlRootElement
from Person
. Unfortunately doing this prevents Person
from being marshaled. It's as if JAXB starts at the base class and works its way down until it finds the first @XmlRootElement
with the appropriate name. I've tried adding a createPerson()
method which returns ReceiverPerson
to ObjectFactory
but that doesn't help.
Upvotes: 46
Views: 70895
Reputation: 2779
Create a custom ObjectFactory to instantiate the desired class during unmarshalling. Example:
JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;
public class ReceiverPersonObjectFactory extends ObjectFactory {
public Person createPerson() {
return new ReceiverPerson();
}
}
Upvotes: 7
Reputation: 12187
Subclass Person twice, once for receiver and once for sender, and only put the XmlRootElement on these subclassses (leaving the superclass, Person
, without an XmlRootElement). Note that sender and receiver both share the same JAXB base classes.
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
// receiver specific code
}
@XmlRootElement(name="person")
public class SenderPerson extends Person {
// sender specific code (if any)
}
// note: no @XmlRootElement here
public class Person {
// data model + jaxb annotations here
}
[tested and confirmed to work with JAXB]. It circumvents the problem you note, when multiple classes in the inheritance hierarchy have the XmlRootElement annotation.
This is arguably also a neater and more OO approach, because it separates out the common data model, so it's not a "workaround" at all.
Upvotes: 13
Reputation: 12187
Since you really have two separate apps, compile them with different versions of the class "Person" - with the receiver app not having @XmlRootElement(name="person")
on Person
. Not only is this ugly, but it defeats the maintainability you wanted from using the same definition of Person for both sender and receiver. Its one redeeming feature is that it works.
Upvotes: -1
Reputation: 570615
The following snippet is a method of a Junit 4 test with a green light:
@Test
public void testUnmarshallFromParentToChild() throws JAXBException {
Person person = new Person();
int age = 30;
String name = "Foo";
person.name = name;
person.age= age;
// Marshalling
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(person, writer);
String outString = writer.toString();
assertTrue(outString.contains("</person"));
// Unmarshalling
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(outString);
RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);
assertEquals(name, reciever.name);
assertEquals(age, reciever.age);
}
The important part is the use of the JAXBContext.newInstance(Class... classesToBeBound)
method for the unmarshalling context:
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
With this call, JAXB will compute a reference closure on the classes specified and will recognize RecieverPerson
. The test passes. And if you change the parameters order, you'll get a java.lang.ClassCastException
(so they must be passed in this order).
Upvotes: 21
Reputation: 19463
You're using JAXB 2.0 right? (since JDK6)
There is a class:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
which one can subclass, and override following methods:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
Example:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>{
@Override public Person unmarshal(ReceiverPerson v){
return v;
}
@Override public ReceiverPerson marshal(Person v){
return new ReceiverPerson(v); // you must provide such c-tor
}
}
Usage is done by as following:
@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
@XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
}
I'm pretty sure, by using this concept you can control the marshalling/unmarshalling process by yourself (including the choice the correct [sub|super]type to construct).
Upvotes: 20
Reputation: 61536
I am not sure why you would want to do this... it doesn't seem all that safe to me.
Consider what would happen in ReceiverPerson has additional instance variables... then you would wind up with (I guess) those variables being null, 0, or false... and what if null is not allowed or the number must be greater than 0?
I think what you probably want to do is read in the Person and then construct a new ReceiverPerson from that (probably provide a constructor that takes a Person).
Upvotes: 3