JAXB inheritance - non-abstract base class - java

I'm currently trying to use JaxB but i'm not being very successful with a relatively simple example. My example is the following:
public class A {
private String m_name;
}
public abstract class B_Base extends A {
}
public class B1 extends B_Base {
private String m_value1;
}
public class B2 extends B_Base {
private String m_value2;
}
All my attempts on (even marshalling) have failed. I have looked through Blaise Doughan's blog including articles such as http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html but none of them seem to help on my example. It is certainly possible that i've misapplied his examples. I would seem to me that my example should be something that is easily supported in JaxB - after all, java is largely based on inheritance relationships!
I would be grateful for a fast response!

You could do the following:
JAXB will pull in super classes, but not subclasses. You can create the JAXBContext on the leaf classes or you can use an #XmlSeeAlso annotation on the parent class to pull in the sub classes.
You will need to provide root element information. Below I have done this with a JAXBElement.
Demo
import javax.xml.bind.*;
import javax.xml.namespace.QName;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(B1.class, B2.class);
B1 b1 = new B1();
JAXBElement<A> jaxbElement = new JAXBElement<A>(new QName("root"), A.class, b1);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(jaxbElement, System.out);
}
}
Output
Below is the output from running the demo code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b1"/>
UPDATE
hi, probably a really stupid question, but just wondering, how would I
adapt this if I have a class C that holds an ArrayList of A objects
(or subclasses)?
Java Model
C
Here is the C class as described in your comment:
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class C {
private List<A> as = new ArrayList<A>();
#XmlElement(name="a")
public List<A> getAs() {
return as;
}
}
A
Here is how you can leverage the #XmlSeeAlso annotation to bring in the subclasses.
#XmlSeeAlso({ B1.class, B2.class })
public class A {
private String m_name;
}
Demo Code
Below is some demo code to show everything works. Note now that we use #XmlSeeAlso we use #XmlSeeAlso we don't need to include the subclasses when bootstrapping JAXBContext.
Demo
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(C.class);
C root = new C();
root.getAs().add(new B1());
root.getAs().add(new B2());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code.
<?xml version="1.0" encoding="UTF-8"?>
<c>
<a xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b1"/>
<a xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b2"/>
</c>

Related

JAXB Serialization Failure with Generic Class

I am trying to write a class that can serailize and deserailize settings to XML using Java. I have this code successfully written in C# and it is very useful so I would like something similar in my java app.
I have the following base class that every class I want t serialize to XML must implement.
package serializers;
import java.lang.reflect.ParameterizedType;
abstract class XmlSerializableObject<T> {
abstract T getDefault();
abstract String getSerializedFilePath();
String getGenericName() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();
}
ClassLoader getClassLoader() {
return ((Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]).getClassLoader();
}
}
where the getGenericName and getClassLoader are for use with instantiating the JAXBContext. I then have a basic implementation of this as a settings provider
public class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
private Settings settings;
#Override
public SettingsProvider getDefault() {
return null;
}
#Override
public String getSerializedFilePath() {
return "C:\\Data\\__tmp.settings";
}
public Settings getSettings() {
return settings;
};
public void setSettings(Settings settings) {
this.settings = settings;
}
}
class Settings {
private String tmp;
public String getTmp() {
return tmp;
}
public void setTmp(String tmp) {
this.tmp = tmp;
}
}
Now I have the following serializer class
package serializers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class XmlSerializer {
private static final Logger logger = LoggerFactory.getLogger(XmlSerializer.class);
public static <T extends XmlSerializableObject> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
String name = o.getGenericName();
ClassLoader classLoader = o.getClassLoader();
// THE FOLLOWING LINE throws.
JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); // also tried JAXBContext.newInstance(name);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
// Deserialize below.
}
I then have the following test to check the results of serialization
package serializers;
import org.junit.Before;
import org.junit.Test;
public class XmlSerializerTest {
private Settings settings = new Settings();
private SettingsProvider provider;
#Before
public void setUp() throws Exception {
settings.setTmp("testing");
provider = new SettingsProvider();
provider.setSettings(settings);
}
#Test
public void serialize() throws Exception {
XmlSerializer.Serialize(provider);
}
}
The problem is the call to JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); which throws
javax.xml.bind.JAXBException: Provider com.sun.xml.internal.bind.v2.ContextFactory could not be instantiated: javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index
- with linked exception:
[javax.xml.bind.JAXBException: "serializers.SettingsProvider" doesnt contain ObjectFactory.class or jaxb.index]
I have tried with and without the ClassLoader object to no avail. How can I serialize a generic type in this way?
Thanks for your time.
Let us look at the line of code that is throwing the exception:
JAXBContext jaxbContext = JAXBContext.newInstance(name);
In the above line of code, the argument name that you are passing is the name of the class that is to be deserialized and is determined at runtime (viz., serializers.SettingsProvider in the given sample). This may not be sufficient for JAXB to determine all the classes that constitutes the JAXB context. So instead, try passing the name of the package(s) that contain all the classes that this instance of JAXBContext should be deserializing -- all the classes in that package(s) is your JAXB context. This is something that will be known at compile time. So, try the following line of code instead:
JAXBContext jaxbContext = JAXBContext.newInstance("serializers");
Here, "serializers" is the name of the package that contains all the classes that you want to be deserializing, i.e., the JAXB context for the given sample.
You may like to refer the Oracle JAXB tutorial and note the following lines of code:
import primer.po.*;
...
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
Please refer this Javadoc and note that in case the classes to be deserialized are spread over multiple packages, then a list of colon separated package names should be passed, e.g.,--
JAXBContext.newInstance( "com.acme.foo:com.acme.bar" )
In case you must pass class names instead of package names, then first read this Javadoc very carefully. Note that the JAXBContext instance will be initialized only with classes passed as parameter and the classes that are statically reachable from these classes. Prefer to write your program in such a way that class names being passed are known at compile time.
Also, it may be helpful for you to note that generics in Java are different (especially w.r.t type erasure) than those in C# -- please see What is the concept of erasure in generics in Java?.
Also, given the class declaration:
class XmlSerializableObject<T> {
}
which states that the class XmlSerializableObject deals with type T, the following class declaration:
class SettingsProvider extends XmlSerializableObject<SettingsProvider> {
}
which states that the class SettingsProvider deals with its own type sounds convoluted.
Or did you instead mean it to declare like as follows:
class SettingsProvider extends XmlSerializableObject<Settings> {
}
which states that the class SettingsProvider deals with type Settings?
That looks like it should be JAXBContext.newInstance(SettingsProvider.class) .
The JAXBContext.newInstance(String ...) versions of the method are expecting a package name, which as the error message says should then contain an ObjectFactory class, or jaxb.index list to guide it to the classes.
You are using this newInstance method :
Parameters:
contextPath - list of java package names that contain schema derived class and/or java to schema (JAXB-annotated) mapped classes
classLoader - This class loader will be used to locate the implementation classes.
So df778899 is right, you should not use this signature as getGenericName returns a fully qualified class name and not a package. And even if it was a package, you will still miss ObjectFactory.class or jaxb.index
But JAXBContext.newInstance(SettingsProvider.class) won't work either. You will get a MarshalException indicating that #XmlRootElement is missing
Annotate SettingsProvider like this :
#XmlRootElement(name = "root")
static class SettingsProvider extends XmlSerializableObject<SettingsProvider>
{
private Settings settings;
// [...]
And finally you will get :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<settings>
<tmp>testing</tmp>
</settings>
</root>
This was done by using the following interfaces
public interface IXmlSerializableObject {
String getSerializedFilePath();
}
The crucial one being
public interface IPersistanceProvider<T> extends IXmlSerializableObject {
void save();
void restoreDefaults();
Class<T> getTypeParameterClass();
}
The crucial property is Class<T> getTypeParameterClass(). This is then used in
public static <T extends PersistanceProviderBase> void Serialize(T o) {
String filePath = o.getSerializedFilePath();
File file = new File(filePath);
try {
JAXBContext jaxbContext = JAXBContext.newInstance(o.getTypeParameterClass());
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(o, file);
} catch (JAXBException e) {
logger.error("Serialization failed", e);
}
}
where PersistanceProviderBase implements the IPersistanceProvider interface.

Can't get JAXB to handle interfaces with simple example

I'm trying the simple example for JAXB Interfaces shown at Unofficial JAXB Guide - Mapping interfaces — Project Kenai, section 3.2.1 and it won't work for me. I'm in latest JDK 1.8_70 and not using any special libraries. Code for completeness sake:
#XmlRootElement
class Zoo {
#XmlAnyElement
public List<Animal> animals;
}
interface Animal {
void sleep();
void eat();
...
}
#XmlRootElement
class Dog implements Animal { ... }
#XmlRootElement
class Lion implements Animal { ... }
Any help on this? The error I'm getting is:
[com.sun.istack.internal.SAXException2: class testjaxb.Cat nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class testjaxb.Cat nor any of its super class is known to this context.]
EDIT: Posted JAXBContext.newInstance code:
Zoo zoo = new Zoo();
zoo.animals = new ArrayList<Animal>();
zoo.animals.add( new Cat() );
zoo.animals.add( new Dog() );
zoo.animals.add( new Dog() );
JAXBContext ctx = JAXBContext.newInstance(Zoo.class);
Marshaller marshaller = ctx.createMarshaller();
marshaller.marshal(zoo, System.out);
Try specifying the other classes in the list you provide to JAXBContext.newInstance().
JAXBContext ctx = JAXBContext.newInstance(Zoo.class, Cat.class, Dog.class);
Applying the #XmlSeeAlso annotation to your Zoo class should also work.
#XmlRootElement
#XmlSeeAlso({Cat.class, Dog.class})
class Zoo {
...
}

JAXB: Problem deserializing a class B that extends a class A

Please consider the following example:
There is a ClassA and a ClassB which extends it. My problem is now that I have to unmarshall a ClassB from an xml file. Please note that ClassA can not be changed as it is not under my control.
Several problems are noted in this example:
The main problem is that ClassA does not have a default no-arg constructor which is required by JAXB without Adapter. Therefore I implemented MyAdapter which maps ClassB to the simple class ValB which can be processed by JAXB without any problems.
The main problem is how to make JAXB use this adapter? Neither defining the #XmlJavaTypeAdapter on class level nor registering the Adapter to the unmarshaller does it.
Does anybody know how to make JAXB use MyAdapter so that the unmarshaller returns an object that is an instance of ClassA?
public class JaxbTest {
public static abstract class ClassA {
public ClassA(String id) {
}
}
#XmlRootElement
#XmlJavaTypeAdapter(MyAdapter.class) // does not have an effect
public static class ClassB extends ClassA {
public String text;
public ClassB() {
super("");
}
}
public static class ValB {
public String text;
}
public static class MyAdapter extends XmlAdapter<ValB, ClassB> {
#Override
public ClassB unmarshal(ValB v) throws Exception {
ClassB b = new ClassB();
b.text = v.text;
return b;
}
#Override
public ValB marshal(ClassB v) throws Exception {
ValB b = new ValB();
b.text = v.text;
return b;
}
}
public static void main(String[] args) {
try {
JAXBContext context = JAXBContext.newInstance(ClassB.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setAdapter(new MyAdapter()); // does not have an effect
ClassA a = (ClassA) unmarshaller.unmarshal(new File("test.xml"));
// do somthing with a
} catch (Exception e) {
e.printStackTrace();
}
}
}
BTW: Don't take the code too serious - it is just an example demonstrating the problem. I know that the definition of ClassA and ClassB are not really useful.
UPDATE
We have addressed this issue in the upcoming EclipseLink JAXB (MOXy) 2.2.0 release (see bug #332742). In this release abstract classes will not be checked for a no-arg constructor.
Pre-release versions with this fix can be obtained here starting December 18th:
http://www.eclipse.org/eclipselink/downloads/nightly.php
Workaround
This is what the #XmlTransient annotation is for. If possible do the following:
#XmlTransient
public static abstract class ClassA {
public ClassA(String id) {
}
}
If it is not possible to annotate ClassA directly, you could leverage an EclipseLink JAXB (MOXy) extension to do this. MOXy allows you to specify JAXB metadata as an XML file. This is useful when you can't modify a model class:
http://bdoughan.blogspot.com/2010/12/extending-jaxb-representing-annotations.html
Below are some articles explaining #XmlAdapter:
http://bdoughan.blogspot.com/2010/12/jaxb-and-immutable-objects.html
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html

Unmarshalling collections in JaxB

suppose I have this class:
public class A {
private HashMap<String, B> map;
#XmlElement
private void setB(ArrayList<B> col) {
...
}
private ArrayList<B> getB() {
...
}
}
When trying to unmarshall an xml document to this class using JaxB I notice that instead of calling the setB() method and sending me the list of B instances JaxB actually calls the getB() and adds the B instances to the returned list. Why?
The reason I want the setter to be called is that the list is actually just a temporary storage from which I want to build the map field, so I thought to do it in the setter.
Thanks.
thats the way jaxb is handling collections. you have to be sure you have a non null collection when jaxb try to unmarshal.
there is a plugin (never used it myself) but can be helpful:
https://jaxb2-commons.dev.java.net/collection-setter-injector/
Hy,
you can use it with jaxb, it's work !!! (with Maven....)
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-Xcollection-setter-injector</arg>
</args>
<plugins>
<plugin>
<groupId>net.java.dev.vcc.thirdparty</groupId>
<artifactId>collection-setter-injector</artifactId>
<version>0.5.0-1</version>
</plugin>
</plugins>
<schemaDirectory>src/schemas</schemaDirectory>
<generateDirectory>src/main/java</generateDirectory>
<extension>true</extension>
</configuration>
</plugin>
and you get your setter for your Collection
Hope it would help people
bye
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The behaviour you are seeing will vary among JAXB implementations. If you do not initialize a value for the List property then EclipseLink JAXB (MOXy) will call the set method as you expect.
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2011/01/jaxb-and-choosing-list-implementation.html
EXAMPLE
A
package forum1032152;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class A {
private ArrayList<B> b;
#XmlElement
public void setB(ArrayList<B> col) {
System.out.println("Called setB");
for(B b : col) {
System.out.println(b);
}
this.b = col;
}
public ArrayList<B> getB() {
return b;
}
}
B
package forum1032152;
public class B {
}
Demo
package forum1032152;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(A.class);
File xml = new File("src/forum1032152/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.unmarshal(xml);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b></b>
<b></b>
</a>
Output
Called setB
forum1032152.B#8bdcd2
forum1032152.B#4e79f1
JAXB has problems supporting interfaces and abstract classes; it usually doesn't know what subclass to instantiate. The problem is, it's a common pattern to have a class along the lines of:
ArrayList list;
#XMLElement
public List getList() {return this.list;}
To get around this, JAXB doesn't even try to instantiate the property class (e.g. List) derived from the getter/setter pair if it's a Collection. It just assumes that it's non-null and modifiable.
Probably the simplest work around is to mark your business interface with #XMLTransient and add a different getter/setter pair with #XMLElement for the view for the data that you want to expose to JAXB. I usually make these protected rather than public, because I don't care to have the somewhat-goofy JAXB behavior as part of my classes' public contract.
Jaxb2 UnMarshaller defines a listener interface which is called any time an object has been un-marshaled. You can define a custom listener to invoke setter methods on all collections (or on sub-objects). This should be pretty easy to do with any one of the bean utils classes. I'm looking for an existing implementation, though I don't see one.
JAXBContext context = JAXBContext.newInstance( classesToBeBound );
m_unmarshaller = context.createUnmarshaller();
m_unmarshaller.setListener(
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
for (Property p : getBeanProperties(target.getClass()))
if (p.isCollectionType() || p.isCompositeType())
p.invokeSetter(p.invokeGetter());
}
});
If you are using the spring framework, its pretty straightforward:
new Unmarshaller.Listener() {
public void afterUnmarshal(Object target, Object parent) {
BeanWrapper wrapper = new BeanWrapperImpl(target);
for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
if (pd.getPropertyType() != null) {
if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
try {
Method setter = pd.getWriteMethod();
if (setter != null) {
Method getter = pd.getReadMethod();
if (getter != null)
setter.invoke(target, getter.invoke(target));
}
}
catch (Exception ex) {
s_logger.error("can't invoke setter", ex);
}
}
}
}
}
}
You can just use an array instead of List )
The reason I want the setter to be called is that the list is actually
just a temporary storage from which I want to build the map field,
so I thought to do it in the setter.
JAXB can handle maps directly, hence, this could make the call to setB() a moot point. If that is an acceptable solution for you, see the example I maintain on my blog to create an adaptor for maps in JAXB.

JAXB inheritance, unmarshal to subclass of marshaled class

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.
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).
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).
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.
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();
}
}
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).
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.

Categories

Resources