JAXB inheritance, unmarshal to subclass of marshaled class - java

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.

Related

Can I use the Lombok #Builder passing the parent class as parameter?

I want to create a new Child instance passing a Parent and other additional parameters.
For example if I have:
public class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
}
public class Child extends Parent {
public String subValue;
}
With lombok, is there a builder that lets me create a Child instance passing the Parent and the missing value as parameters?
Would be easier if I could write something like:
Parent p = Parent.builder()
.param1("a")
.param2("b")
// many parameters
.paramN("b")
.build();
Child c = Child.builder(p).subValue("c").build();
Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:
The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.
Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.
The regular #Builder is not sufficient here, because you are dealing with a class hierarchy. However, #SuperBuilder was made exactly for such a case.
#SuperBuilder generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code #SuperBuilder generates. You should think about whether this is worth it.
Here's the solution (with Lombok >= 1.18.16):
#SuperBuilder(toBuilder = true)
public static class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
protected B $fillValuesFromParent(Parent instance) {
$fillValuesFromInstanceIntoBuilder(instance, this);
return self();
}
}
}
#SuperBuilder(toBuilder = true)
public static class Child extends Parent {
public String subValue;
public static ChildBuilder<?, ?> toBuilder(Parent p) {
return new ChildBuilderImpl().$fillValuesFromParent(p);
}
}
The new toBuilder method on Child creates a new ChildBuilderImpl (which will create a Child instance when calling build()). To fill the values from the given Parent p, it calls the new $fillValuesFromParent method from ParentBuilder. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.
Also note the $ prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.
I would suggest you use #SuperBuilder
#SuperBuilder was introduced as experimental feature in lombok v1.18.2.
The #SuperBuilder annotation produces complex builder APIs for your
classes. In contrast to #Builder, #SuperBuilder also works with fields
from superclasses. However, it only works for types. Most importantly,
it requires that all superclasses also have the #SuperBuilder
annotation.
#Getter
#SuperBuilder
public class Parent {
public String name;
public String value;
}
#Getter
#SuperBuilder
public class Child extends Parent {
public String subValue;
}
Then all you need to do is
Child.builder().name("a").value("b").subValue("c").build();

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.

Access default JAXB marshaller used for JAXRS resource method

I have a number of REST resource classes that return a model entity and rely upon JAXRS to convert to XML automatically (without my own custom Provider). I want to be able to access the JAXB marshaller instance used for this so I can configure a ValidationEventHandler to catch exceptions. How do I do this?
Here is my sample entity resource:
#Path("/device")
public class DeviceResource extends CaBridgeServletResourceManager {
/**
* Get the server status.
*/
#GET
#Path("/config")
public DeviceConfigurationResponse getDeviceConfigurationResponse() {
DeviceService service = new DeviceService(getSessionContext());
DeviceConfigurationResponse response = service.createConfigurationResponse(getDeviceCredential());
return response;
}
}
I want to be able to do something like:
Marshaller marshaller = ... get jaxrs default marshaller ...
marshaller.setEventHandler(new MyMarshallerEventHandler());
How do I get the default marshaller used by jaxrs? Or is there a new marshaller instance I can access for each instance of my resource class (above)?
I would rather avoid creating custom Provider classes for every entity class I have.
Define a ContextResolver and it will get used:
#Provider
public class JaxbMarshallerProvider implements ContextResolver<Marshaller> {
#Override
public Marshaller getContext(Class<?> type) {
}
}
And the same thing for the Unmarshaller. We generally instantiate the JAXBContext once and stash it in a static member in the provider class.

remove whitespaces from jaxb xml response

I have a WS that generates a SOAP XML message. Due to size limitations I would like to remove the unnecessary whitespaces (used for indentation) and new lines. How can I do this when using generated classes and annotations (#WebService and #WebMethod)? In the examples I have seen it is done like this:
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.FALSE);
However, I am not manually creating the Marshaller so I do not know where I can add this property and if this is the correct way of doing it. The JAXB implementation is axis2.
Create a custom JAXBContext and annotate your webservice as mentioned below:
#WebService(serviceName = "Hello")
#UsesJAXBContext(value = CustomJaxbContext.class)
public class HelloWS
{ ...
}
public class HelloJaxbContext implements JAXBContextFactory
{
#Override
public JAXBRIContext createJAXBContext(SEIModel seim, List<Class> classesToBind, List<TypeReference> typeReferences) throws JAXBException {
//JAXBRIContext extends JAXBContext, so you should be able to set the desired marshaller properties
//create your jaxb context with necessary properties for marshalling
return yourJAXBRIContext;
}
}
Refer http://javasourcecode.org/html/open-source/jdk/jdk-6u23/com/sun/xml/internal/ws/developer/JAXBContextFactory.html
<dataFormats><jaxb prettyPrint=false></dataFormats>This pretty Print flag will format the xml file in compressed way and remove crlf

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

Categories

Resources