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
Related
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.
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.
In my application I use Jersey REST to serialize complex objects. This works quite fine. But there are a few method which simply return an int or boolean.
Jersey can't handle primitive types (to my knowledge), probably because they're no annotated and Jersey has no default annotation for them. I worked around that by creating complex types like a RestBoolean or RestInteger, which simply hold an int or boolean value and have the appropriate annotations.
Isn't there an easier way than writing these container objects?
Have a look at Genson.It helped me a lot with a similar problem.With Genson you could use generics like int,boolean, lists and so on...Here is a quick example.
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getMagicList() {
List<Object> objList = new ArrayList<>();
stringList.add("Random String");
stringList.add(121); //int
stringList.add(1.22); //double
stringList.add(false); //bolean
return Response.status(Status.OK).entity(objList).build();
}
This will produce a valid JSON witch can be retrieved very simple like this:
Client client = Client.create();
WebResource webResource = client.resource("...path to resource...");
List objList = webResource.accept(MediaType.APPLICATION_JSON).get(ArrayList.class);
for (Object obj : objList) {
System.out.println(obj.getClass());
}
You will see that Genson will help you decode the JSON on the client side also and output the correct class for each.
Are you writing a service or a client? In the service-end of things, you would simply write a MessageBodyWriter to serialize a stream of data to a Java object for your types. In my use cases, the services I'm writing output to JSON or XML, and in XML's case, I just throw one JAXB annotation on the top of my classes and I'm done.
Have you looked at the Jersey User guide regarding this?
3.6. Adding support for new representations
Actually your best bet is to write a custom ContextResolver Provider like the following that uses natural building of JSON.
#Provider
public class YourContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { YourSpecialBean.class };
public YourContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType)) return context;
return null;
}
}
The only thing special here to notice is the YourSpecialBean.class in the Class[]. This defines an array of class types that this provider will resolve naturally.
Tell Jersey generate proper JSON documents (natural json). I use same class for rest app and JAXBContext resolver, found it the most clean encapsulation.
Better programmer could implement helper to iterate .class files and list appropriate classes automatically by identifying #Annotation tags. I don't know how to do it runtime in an own source code.
These two links were helpful studying this extra java jargon. I don't know why there is no Jersey parameter to make all just work out of the box.
http://jersey.java.net/nonav/documentation/latest/json.html
http://jersey.java.net/nonav/documentation/latest/chapter_deps.html
https://maven.java.net/content/repositories/releases/com/sun/jersey/jersey-archive/
http://search.maven.org/remotecontent?filepath=asm/asm/3.3.1/asm-3.3.1.jar
https://github.com/rmuller/infomas-asl/
WEB-INF/web.xml (snippet):
<servlet>
<servlet-name>RESTServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.myapp.rest.RESTApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RESTServlet</servlet-name>
<url-pattern>/servlet/rest/*</url-pattern>
</servlet-mapping>
com.myapp.rest.RESTApplication.java
package com.myapp.rest;
import java.util.*;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
public class RESTApplication extends Application implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types;
public RESTApplication() throws JAXBException {
// list JAXB bean types to be used for REST serialization
types = new Class[] {
com.myapp.rest.MyBean1.class,
com.myapp.rest.MyBean2.class,
};
context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
#Override
public Set<Class<?>> getClasses() {
// list JAXB resource/provider/resolver classes
Set<Class<?>> classes = new HashSet<Class<?>>();
//for(Class<?> type : types)
// classes.add(type);
classes.add(MyBeansResource.class);
classes.add(this.getClass()); // used as a ContextResolver class
return classes;
}
#Override
public JAXBContext getContext(Class<?> objectType) {
// this is called each time when rest path was called by remote client
for (Class<?> type : types) {
if (type==objectType)
return context;
}
return null;
}
}
Classes MyBean1,MyBean2 are plain java objects and MyBeansResource class is the one with #Path rest functions. There is nothing special in them expect standard jaxp #Annotations here and there. After this java jargon JSON documents have
zero or single-element List arrays are always written as json array ([] field)
primitive integers and boolean fields are written as json primitives (without quotations)
I use the following environment
Sun Java JDK1.6.x
Apache Tomcat 6.x
Jersey v1.14 libraries (jersey-archive-1.14.zip)
webapps/myapp/WEB-INF/lib folder has asm-3.3.1.jar, jackson-core-asl.jar, jersey-client.jar, jersey-core.jar, jersey-json.jar, jersey-server.jar, jersey-servlet.jar libraries
add optional annotation-detector.jar if you use infomas-asl discovery tool
jersey-archive.zip had older asm-3.1.jar file, probably works fine but chapter_deps.html links to a newer file. See link list at the top.
Edit
I found an excellent(fast, lightweight just 15KB) annotation discovery tool. See this post about how I autodiscover types at runtime and no longer need to edit RESTApplication each time new java(jaxb) bean is added.
https://github.com/rmuller/infomas-asl/issues/7
I had the same problem today and didn't give up until i found a really good suitable solution. I can not update the jersey library from 1.1.5 it is a Legacy System. My Rest Service returns a List and they should follow those rules.
Empty Lists are rendered as [] (almost impossible)
One Element Lists are rendered as [] (difficult but only mapping configuration)
Many Element Lists are rendered as [] (easy)
Start from easy to impossible.
3) nothing today normal JSON Mapping
2) Register JAXBContextResolver like the following
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private Class<?>[] ctypes = { Pojo.class }; //your pojo class
public JAXBContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(ctypes));
this.context = new JSONJAXBContext(JSONConfiguration.mapped()
.rootUnwrapping(true)
.arrays("propertyName") //that should rendered as JSONArray even if the List only contain one element but doesn't handle the empty Collection case
.build()
, ctypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
1) The following approach only works for Collections$EmptyList class. May you find a way to make it general for all Collections they are empty. May code deal with EmptyList so.
#Provider
#Produces(value={MediaType.APPLICATION_JSON})
public class EmptyListWriter implements MessageBodyWriter<AbstractList> {
private static final String EMPTY_JSON_ARRAY = "[]";
#Override
public long getSize(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return EMPTY_JSON_ARRAY.length();
}
#Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.getName().equals("java.util.Collections$EmptyList");
}
#Override
public void writeTo(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> headers, OutputStream outputStream) throws IOException, WebApplicationException {
if (list.isEmpty())
outputStream.write(EMPTY_JSON_ARRAY.getBytes());
}
}
I've just discovered that returning a primitive type with Jersey is problematic. I've decided to return String instead. Maybe this is not clean, but I don't think it's too dirty. The Java client, which is written by the same author of the server most of the times, can wrap such a string return value and convert it back to int. Clients written in other languages must be aware of return types any way.
Defining RestInteger, RestBoolean may be another option, however it's more cumbersome and I see too little advantage in it to be attractive.
Or maybe am I missing something important here?
I am trying to get JAXB to work with a groovy class of mine, however, it appears it doesn't work but the java version does. Here is the code...
Here are the Scenarios:
If 2 and 3 are uncommented it works fine.
If 1 and 4 are uncommented I get:
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException:
2 counts of IllegalAnnotationExceptions
groovy.lang.MetaClass is an interface, and JAXB can't handle interfaces.
If 1 and 5 are uncommented I get:
javax.xml.bind.JAXBException: class org.oclc.presentations.simplejaxb.PlayerGroovy
nor any of its super class is known to this context.
Any ideas?
Java:
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Player {
}
Groovy:
import javax.xml.bind.annotation.XmlRootElement
#XmlRootElement
public class PlayerGroovy {
}
Test:
import org.junit.Test
import javax.xml.bind.JAXBContext
import javax.xml.bind.Marshaller
import org.junit.Assert
class PlayerTest {
#Test
public void testJaXB(){
//1 PlayerGroovy player = new PlayerGroovy()
//2 Player player = new Player()
StringWriter writer = new StringWriter();
//3 JAXBContext context = JAXBContext.newInstance(Player.class);
//4 JAXBContext context = JAXBContext.newInstance(PlayerGroovy.class);
//5 JAXBContext context = JAXBContext.newInstance(PlayerGroovy.getClass());
Marshaller m = context.createMarshaller();
m.marshal(player, writer);
println(writer)
Assert.assertTrue(true)
}
}
Uncommenting 1 and 4 is the correct way to set JAXB up with Groovy. The reason it is not working is that each Groovy Class has a metaClass property on it. JAXB is trying to expose this as a JAXB property which obviously fails. Since you don't declare the metaClass property yourself, it is not possible to annotate it to have JAXB ignore it. Instead you and set the XmlAccessType to NONE. This disable's JAXB's auto-discovery of properties to expose as XML elements. After you do that, you need to explicitly declare any fields you want exposed.
Example:
#XmlAccessorType( XmlAccessType.NONE )
#XmlRootElement
public class PlayerGroovy {
#XmlAttribute
String value
}
I was having the same problem while exposing a Grails GORM object. After researching the solution posted above, using #XmlAccessorType( XmlAccessType.NONE ), I quickly grew tired of marking everything as #XmlAttribute.
I'm having plenty of success using:
#XmlAccessorType( XmlAccessType.FIELD )
#XmlRootElement
public class PlayerGroovy {
String value
}
See: XmlAccessType
Thanks to the original answer for getting me started in the right direction.
The solution does not seem to work on an abstract subclass. I think it's because the compiler does not generate the getMetaClass override code. I ended up mimicking the steps from this question as follows:
#XmlAccessorType(XmlAccessType.NONE)
package groovy.lang;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
Yes, it's kinda weird. In my case, I have code like this:
package pkg;
abstract class ScriptMomma extends groovy.lang.Script {
// write some nice supporting code here
}
And to execute my scripts, I have:
def config = new org.codehaus.groovy.control.CompilerConfiguration()
config.scriptBaseClass = 'pkg.ScriptMomma'
ScriptMomma mine = new GroovyShell(config).evaluate(scriptFile, 'TheOne')
I'd prefer a better solution, but right now this is all I have.
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.