How to handle Enum with Generics in JAXB? - java

JAXB runtime is failing to create JAXBContext for a Class whose member variable is defined as
#XmlElement(name = "EnumeraatioArvo")
private Enum<?> eenum;
How to handle such scenario in JAXB?

I agree with skaffman that this defeats the purpose on enums. If for some reason this is something you need to do, you could try the following:
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Root {
private Enum<?> eenum;
#XmlJavaTypeAdapter(EnumAdapter.class)
public Enum<?> getEenum() {
return eenum;
}
public void setEenum(Enum<?> eenum) {
this.eenum = eenum;
}
}
Below are two sample Enums we will use in this example:
public enum Compass {
NORTH, SOUTH, EAST, WEST
}
public enum Suit {
CLUBS, SPADES, DIAMONDS, HEARTS
}
We need to use an XmlAdapter on the eenum property. The XmlAdapter will need to know about all the possible types of Enums that are valid for this property.
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class EnumAdapter extends XmlAdapter<String, Enum> {
private Class[] enumClasses = {Compass.class, Suit.class};
#Override
public Enum unmarshal(String v) throws Exception {
for(Class enumClass : enumClasses) {
try {
return (Enum) Enum.valueOf(enumClass, v);
} catch(IllegalArgumentException e) {
}
}
return null;
}
#Override
public String marshal(Enum v) throws Exception {
return v.toString();
}
}
You can verify this works with the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<eenum>SPADES</eenum>
</root>
By using the following code:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}

Related

JAXB Marshall String to base64

I need to marshall an object which includes a String variable.
The String var. contains an XML document, and it gets marshalled with escaping to XMLElement.
I'd like to marshall the String variable to base64 format, and back to String on unmarshall.
Is this posiible?
You can use an XmlAdapter to convert the String to/from a byte[] during the marshalling/unmarshalling process. By default JAXB will represent a byte[] as base64Binary.
XmlAdapter (Base64Adapter)
Below is an XmlAdapter that will convert between a String and a byte[].
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class Base64Adapter extends XmlAdapter<byte[], String> {
#Override
public String unmarshal(byte[] v) throws Exception {
return new String(v);
}
#Override
public byte[] marshal(String v) throws Exception {
return v.getBytes();
}
}
Java Model (Foo)
The XmlAdapter is configured using the #XmlJavaTypeAdapter annotation.
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Foo {
private String bar;
#XmlJavaTypeAdapter(Base64Adapter.class)
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
Demo
In the demo code below we will create an instance of Foo and marshal it to XML.
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Foo foo = new Foo();
foo.setBar("<abc>Hello World</abc>");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
Output
Below is the output from running the demo code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo>
<bar>PGFiYz5IZWxsbyBXb3JsZDwvYWJjPg==</bar>
</foo>

JAXB - element value field

I have a class, that is something like this:
public class Property {
private double floorArea;
public double getFloorArea() {
return floorArea;
}
#XmlElement
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
Which will give me something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>x</floorArea>
</property>
But I need something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>
<value>x</value>
</floorArea>
</property>
The API I am using requires it this way. My limited JAXB knowledge is preventing me from figuring this out. Any help is appreciated.
EDIT:
something I am researching. Would I need to create a value class with its own JAXB annotations for this to work? (and set floorArea to the type of value)?
Below is how your use case could be supported with an XmlAdapter using any JAXB (JSR-222) implementation.
XmlAdapter (DoubleValueAdapter)
An XmlAdapter is a mechanism that allows an object to be converted to another type of object . Then it is the converted object that is converted to/from XML.
package forum14045961;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DoubleValueAdapter extends XmlAdapter<DoubleValueAdapter.AdaptedDoubleValue, Double>{
public static class AdaptedDoubleValue {
public double value;
}
#Override
public AdaptedDoubleValue marshal(Double value) throws Exception {
AdaptedDoubleValue adaptedDoubleValue = new AdaptedDoubleValue();
adaptedDoubleValue.value = value;
return adaptedDoubleValue;
}
#Override
public Double unmarshal(AdaptedDoubleValue adaptedDoubleValue) throws Exception {
return adaptedDoubleValue.value;
}
}
Property
The #XmlJavaTypeAdapter annotation is used to specify the XmlAdapter. I needed to change double to Double so I moved the mapping to the field as to not affect the public API.
package forum14045961;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Property {
#XmlJavaTypeAdapter(DoubleValueAdapter.class)
private Double floorArea;
public double getFloorArea() {
return floorArea;
}
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
Demo
Below is some demo code to prove that everything works.
package forum14045961;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Property.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14045961/input.xml");
Property property = (Property) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(property, System.out);
}
}
input.xml/Output
Below is the input to and output from the demo code.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<property>
<floorArea>
<value>1.23</value>
</floorArea>
</property>
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is how you could map your use case using MOXy's #XmlPath extension:
Property
package forum14045961;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
public class Property {
private double floorArea;
public double getFloorArea() {
return floorArea;
}
#XmlPath("floorArea/value/text()")
public void setFloorArea(double floorArea) {
this.floorArea = floorArea;
}
}
jaxb.properties
To specify MOXy as your JAXB (JSR-222) provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
Only the standard JAXB runtime APIs are required to read the objects from and write them back to XML when MOXy is used as the JAXB provider.
package forum14045961;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Property.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14045961/input.xml");
Property property = (Property) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(property, System.out);
}
}
input.xml/Output
Below is the input to and output from running the demo code.
<?xml version="1.0" encoding="UTF-8"?>
<property>
<floorArea>
<value>1.23</value>
</floorArea>
</property>
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
Your hunch is correct the way described will generate the xml as you have displayed.
public class Property {
#XmlElement(required = true)
protected FloorArea floorArea;
public FloorArea getFloorArea() {
return floorArea;
}
public void setFloorArea(FloorArea value) {
this.floorArea = value;
}
}
And your FloorArea class would look something like the code snapshot below.
public class FloorArea {
#XmlElement(required = true)
protected String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Parametrizing JAXB factory methods with class to create

According to the documentation JAXB factory methods do not have arguments. Is there a JAXB implementation that allow me to create a factory method that receives as a parameter the class of the object I need to create ?
It happens that all my JAXB objects follow the same creation pattern (a particular byte code instrumentation), therefore I would like to encapsulate this in one single factory method having as a parameter the class of the JAXB object to create, avoiding in this way the creation of different factory methods for each JAXB class that basically do exactly the same thing.
I found someone asking the same question in an OTN forum: https://forums.oracle.com/forums/thread.jspa?messageID=9969927#9969927, but not a real answer has been proposed yet.
Thanks for any help
This is currently not possible using the standard JAXB APIs. I have entered the following enhancement request to have this behaviour added to EclipseLink JAXB (MOXy):
https://bugs.eclipse.org/363192
MOXy Specific Solution
You could leverage the #XmlCustomizer extension in EclipseLink JAXB (MOXy) to customize how the objects are instantiated. This mechanism is leveraged to tweak MOXy's underlying metadata.
CommonFactory
import java.util.Date;
public class CommonFactory {
public static Object create(Class<?> clazz) {
if(Foo.class == clazz) {
return new Foo(new Date());
} else if(Bar.class == clazz) {
return new Bar(new Date());
}
return null;
}
}
Foo.class
The Foo class is annotated normally except that we will use the #XmlCustomizer annotation to specify a DescriptorCustomizer that we are going to use to tweak MOXy's metadata.
import java.util.Date;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;
#XmlRootElement
#XmlType(factoryClass=CommonFactory.class, factoryMethod="create")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(FactoryCustomizer.class)
public class Foo {
private Date creationDate;
private Bar bar;
// Non-default constructor
public Foo(Date creationDate) {
this.creationDate = creationDate;
}
}
Bar
Again we will use the #XmlCustomizer annotation to reference the same DescriptorCustomizer that we did in the Foo class.
import java.util.Date;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlCustomizer;
#XmlType(factoryClass=CommonFactory.class, factoryMethod="create")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(FactoryCustomizer.class)
public class Bar {
private Date creationDate;
// Non-default constructor
public Bar(Date creationDate) {
this.creationDate = creationDate;
}
}
FactoryCustomizer
MOXy has the concept of an InstantiationPolicy to build new objects. In this example we will swap in our own instance InstantiationPolicy that can use parameterized factory methods:
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.sessions.AbstractSession;
public class FactoryCustomizer implements DescriptorCustomizer{
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.setInstantiationPolicy(new MyInstantiationPolicy(descriptor));
}
private static class MyInstantiationPolicy extends InstantiationPolicy {
public MyInstantiationPolicy(ClassDescriptor descriptor) {
InstantiationPolicy defaultInstantiationPolicy = descriptor.getInstantiationPolicy();
this.factoryClassName = defaultInstantiationPolicy.getFactoryClassName();
this.factoryClass = defaultInstantiationPolicy.getFactoryClass();
this.methodName = defaultInstantiationPolicy.getMethodName();
}
#Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
}
#Override
protected void initializeMethod() throws DescriptorException {
Class<?>[] methodParameterTypes = new Class[] {Class.class};
try {
this.method = PrivilegedAccessHelper.getMethod(factoryClass, methodName, methodParameterTypes, true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
#Override
public Object buildNewInstance() throws DescriptorException {
Object[] parameters = new Object[] {this.descriptor.getJavaClass()};
try {
return PrivilegedAccessHelper.invokeMethod(method, factory, parameters);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Demo
import java.io.StringReader;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Foo foo = (Foo) unmarshaller.unmarshal(new StringReader("<foo><bar/></foo>"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<creationDate>2011-11-08T12:35:43.198</creationDate>
<bar>
<creationDate>2011-11-08T12:35:43.198</creationDate>
</bar>
</foo>
For More Information
http://blog.bdoughan.com/2011/06/jaxb-and-factory-methods.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

How to marshall an objectgraph with Java XML binding to a specific depth?

being a newbie to Java XML binding i am facing a challenge.
Let say i have a scenario where my domain model is constructed and i want to marshall this domain to an xml structure.
Now i want to provide different unmarshall path's:
Marshall the whole object graph [no problem here]
Marshall an objectgraph until a specific depth!!! [challenge]
I cannot figure out a good way on how to tackle this without introducing to much complexity. One can make a copy of the domain and manually later that, but that does not feel right. Any other solutions available?
You could leverage XmlAdapter and Marshal.Listener to get this behaviour:
Demo
A Marshal.Listener will be set to keep track of the depth of the tree we are marshalling. Also we will set runtime level XmlAdapters that are aware of the depth listener. These adapters will start returning null when the desired depth has been reached.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root rootA = new Root();
rootA.setName("A");
Root rootB = new Root();
rootB.setName("B");
rootA.setChild(rootB);
Root rootC = new Root();
rootC.setName("C");
rootB.setChild(rootC);
Root rootD = new Root();
rootD.setName("D");
rootC.setChild(rootD);
Root rootE = new Root();
rootE.setName("E");
rootD.setChild(rootE);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
DepthListener depthListener = new DepthListener(3);
marshaller.setListener(depthListener);
marshaller.setAdapter(new RootAdapter(depthListener));
marshaller.marshal(rootA, System.out);
}
}
DepthListener
The purpose of this class is to to keep track of the current depth.
import javax.xml.bind.Marshaller;
public class DepthListener extends Marshaller.Listener {
private int targetDepth;
private int currentDepth = 0;
public DepthListener(int depth) {
this.targetDepth = depth;
}
#Override
public void beforeMarshal(Object source) {
currentDepth++;
}
#Override
public void afterMarshal(Object source) {
currentDepth--;
}
public boolean isMarshalDepth() {
return currentDepth <= targetDepth;
}
}
RootAdapter
The purpose of the XmlAdapter is to start returning null when the desired depth has been reached to stop the marshalling process.
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class RootAdapter extends XmlAdapter<Root, Root> {
private DepthListener depthListener;
public RootAdapter() {
}
public RootAdapter(DepthListener depthListener) {
this.depthListener = depthListener;
}
#Override
public Root unmarshal(Root root) throws Exception {
return root;
}
#Override
public Root marshal(Root root) throws Exception {
if(depthListener != null && !depthListener.isMarshalDepth()) {
return null;
}
return root;
}
}
Root
The following demonstrates how to specify the XmlAdapter on the domain object via the #XmlJavaTypeAdapter annotation:
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlJavaTypeAdapter(RootAdapter.class)
#XmlType(propOrder={"name", "child"})
public class Root {
private String name;
private Root child;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Root getChild() {
return child;
}
public void setChild(Root report) {
this.child = report;
}
}
Output
The following is the output from the demo code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<name>A</name>
<child>
<name>B</name>
<child>
<name>C</name>
</child>
</child>
</root>

JAXB element name based on object property

I have to create object model for following XMLs:
XML sample 1:
<InvoiceAdd>
<TxnDate>2009-01-21</TxnDate>
<RefNumber>1</RefNumber>
<InvoiceLineAdd>
</InvoiceLineAdd>
</InvoiceAdd>
XML Sample 2:
<SalesOrderAdd>
<TxnDate>2009-01-21</TxnDate>
<RefNumber>1</RefNumber>
<SalesOrderLineAdd>
</SalesOrderLineAdd>
</SalesOrderAdd>
The XML output will be based on a single string parameter or enum. String txnType = "Invoice"; (or "SalesOrder");
I would use single class TransactionAdd:
#XmlRootElement
public class TransactionAdd {
public String txnDate;
public String refNumber;
private String txnType;
...
public List<LineAdd> lines;
}
instead of using subclasses or anything else. The code which creates the TransactionAdd instance is the same for both types of transaction it only differs on the type.
This XML is used by a rather known product called QuickBooks and is consumed by QuickBooks web service - so I can't change the XML, but I want to make it easy to be able to set element name based on property (txnType).
I would consider something like a method to determine target element name:
#XmlRootElement
public class TransactionAdd {
public String txnDate;
public String refNumber;
private String txnType;
...
public List<LineAdd> lines;
public String getElementName() {
return txnType + "Add";
}
}
Different transactions will be created using following code:
t = new TransactionAdd();
t.txnDate = "2010-12-15";
t.refNumber = "123";
t.txnType = "Invoice";
The goal is to serialize t object with the top-level element name based on txnType. E.g.:
<InvoiceAdd>
<TxnDate>2009-01-21</TxnDate>
<RefNumber>1</RefNumber>
</InvoiceAdd>
In case of t.txnType = "SalesOrder" the result should be
<SalesOrderAdd>
<TxnDate>2009-01-21</TxnDate>
<RefNumber>1</RefNumber>
</SalesOrderAdd>
At the moment I see only one workaround with subclasses InvoiceAdd and SalesOrderAdd and using #XmlElementRef annotation to have a name based on class name. But it will need to instantiate different classes based on transaction type and also will need to have two other different classes InvoiceLineAdd and SalesOrderLineAdd which looks rather ugly.
Please suggest me any solution to handle this. I would consider something simple.
To address the root element aspect you could do will need to leverage #XmlRegistry and #XmlElementDecl. This will give us multiple possible root elements for the TransactionAdd class:
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="InvoiceAdd")
JAXBElement<TransactionAdd> createInvoiceAdd(TransactionAdd invoiceAdd) {
return new JAXBElement<TransactionAdd>(new QName("InvoiceAdd"), TransactionAdd.class, invoiceAdd);
}
#XmlElementDecl(name="SalesOrderAdd")
JAXBElement<TransactionAdd> createSalesOrderAdd(TransactionAdd salesOrderAdd) {
return new JAXBElement<TransactionAdd>(new QName("SalesOrderAdd"), TransactionAdd.class, salesOrderAdd);
}
}
Your TransactionAdd class will look something like the following. The interesting thing to note is that we will make the txnType property #XmlTransient.
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
public class TransactionAdd {
private String txnDate;
private String refNumber;
private String txnType;
private List<LineAdd> lines;
#XmlElement(name="TxnDate")
public String getTxnDate() {
return txnDate;
}
public void setTxnDate(String txnDate) {
this.txnDate = txnDate;
}
#XmlElement(name="RefNumber")
public String getRefNumber() {
return refNumber;
}
public void setRefNumber(String refNumber) {
this.refNumber = refNumber;
}
#XmlTransient
public String getTxnType() {
return txnType;
}
public void setTxnType(String txnType) {
this.txnType = txnType;
}
public List<LineAdd> getLines() {
return lines;
}
public void setLines(List<LineAdd> lines) {
this.lines = lines;
}
}
Then we need to supply a little logic outside the JAXB operation. For an unmarshal we will use the local part of the root element name to populate the txnType property. For a marshal we will use the value of the txnType property to create the appropriate JAXBElement.
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(TransactionAdd.class, ObjectFactory.class);
File xml = new File("src/forum107/input1.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<TransactionAdd> je = (JAXBElement<TransactionAdd>) unmarshaller.unmarshal(xml);
TransactionAdd ta = je.getValue();
ta.setTxnType(je.getName().getLocalPart());
JAXBElement<TransactionAdd> jeOut;
if("InvoiceAdd".equals(ta.getTxnType())) {
jeOut = new ObjectFactory().createInvoiceAdd(ta);
} else {
jeOut = new ObjectFactory().createSalesOrderAdd(ta);
}
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(jeOut, System.out);
}
}
To Do
I will look into addressing the lines property next.
You could use an XmlAdapter for this. Based on the String value of the txnType property you would have the XmlAdapter marshal an instance of an Object corresponding to InvoiceLineAdd or SalesOrderLineAdd.
This is how it would look:
TransactionAdd
On the txnType property we will use a combination of #XmlJavaTypeAdapter and #XmlElementRef:
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class TransactionAdd {
private String txnType;
#XmlJavaTypeAdapter(MyAdapter.class)
#XmlElementRef
public String getTxnType() {
return txnType;
}
public void setTxnType(String txnType) {
this.txnType = txnType;
}
}
The adapted objects will look like:
AbstractAdd
import javax.xml.bind.annotation.XmlSeeAlso;
#XmlSeeAlso({InvoiceAdd.class, SalesOrderAdd.class})
public class AbstractAdd {
}
InvoiceAdd
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class InvoiceAdd extends AbstractAdd {
}
SalesOrderAdd
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class SalesOrderAdd extends AbstractAdd {
}
The XmlAdapter to convert between the String and the adapted objects will look like:
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class MyAdapter extends XmlAdapter<AbstractAdd, String> {
#Override
public String unmarshal(AbstractAdd v) throws Exception {
if(v instanceof SalesOrderAdd) {
return "salesOrderAdd";
}
return "invoiceAdd";
}
#Override
public AbstractAdd marshal(String v) throws Exception {
if("salesOrderAdd".equals(v)) {
return new SalesOrderAdd();
}
return new InvoiceAdd();
}
}
The following demo code can be used:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(TransactionAdd.class);
File xml = new File("input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
TransactionAdd ta = (TransactionAdd) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(ta, System.out);
}
}
To produce/consume the following XML:
<transactionAdd>
<salesOrderAdd/>
</transactionAdd>
For more information see:
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html

Categories