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
Related
I have defined the following ObjectFactory:
#XmlRegistry
public class ObjectFactory {
public Dogs createDogs() {
return new Dogs();
}
#XmlElementDecl(name = "dog")
public Dog createDog(DogType value) {
return new Dog(value);
}
#XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public Dog createFido(DogType value) {
return new Dog("fido", value);
}
#XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public Dog createBarks(DogType value) {
return new Dog("barks", value);
}
}
(Dogs class is trivial, Dog and DogType see below or here.)
I am unmarshalling the following XML:
<listOfDogs>
<dogs>
<dog>
<name>henry</name>
<sound>bark</sound>
</dog>
<fido>
<sound>woof</sound>
</fido>
<barks>
<sound>miau</sound>
</barks>
</dogs>
</listOfDogs>
I was sincerely expecting that JAXB will call my createFido(...) and createBarks(...) methods during unmarshalling. But this does not happen. The Dog constructor is invoked directly via reflection, the appropriate create... methods are not used.
My question is:
Why is ObjectFactory not called during unmarshalling?
Shouldn't it be? Or is ist just a dummy to hold the #XmlRegistry/#XmlElementDecl declarations?
I have also checked this question:
What is the ObjectFactory role during JAXB-Unmarshalling?
The solution there is to use #XmlType.factoryClass and factoryMethod. This will not work here because I don't want to statically link my DogType to the certain instantiation routine. I want it to be decided in the runtime based on the element name. My goal is to instantiate the same class but differently, depending on the element name.
Now some code to make it complete.
Root element class:
#XmlRootElement(name = "listOfDogs")
public class Dogs {
private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();
#XmlElementWrapper(name = "dogs")
#XmlElementRef(name = "dog")
public List<JAXBElement<DogType>> getDogs() {
return this.dogs;
}
#Override
public String toString() {
return "Dogs [dogs=" + dogs + "]";
}
}
Dog, the wrapper element class for the DogType:
public class Dog extends JAXBElement<DogType> {
public static final QName NAME = new QName("dog");
private static final long serialVersionUID = 1L;
public Dog(DogType value) {
super(NAME, DogType.class, value);
}
public Dog(String dogName, DogType value) {
super(NAME, DogType.class, value);
}
#Override
public QName getName() {
final DogType value = getValue();
if (value != null && value.getName() != null) {
return new QName(value.getName());
} else {
return super.getName();
}
}
}
DogType:
public class DogType {
private String name;
private String sound;
public String getName() {
return name;
}
public void setName(String dogName) {
this.name = dogName;
}
public String getSound() {
return sound;
}
public void setSound(String sound) {
this.sound = sound;
}
}
Test:
public class DogTest {
#Test
public void unmarshallsDogs() throws JAXBException {
final JAXBContext context = JAXBContext
.newInstance(ObjectFactory.class);
final Dogs dogs = (Dogs) context.createUnmarshaller().unmarshal(
getClass().getResource("dogs.xml"));
Assert.assertEquals(3, dogs.getDogs().size());
// Does not work
// Assert.assertEquals("henry", dogs.getDogs().get(0).getValue()
// .getName());
Assert.assertEquals("bark", dogs.getDogs().get(0).getValue().getSound());
// Does not work
// Assert.assertEquals("fido", dogs.getDogs().get(1).getValue()
// .getName());
Assert.assertEquals("woof", dogs.getDogs().get(1).getValue().getSound());
// Does not work
// Assert.assertEquals("barks", dogs.getDogs().get(2).getValue()
// .getName());
Assert.assertEquals("miau", dogs.getDogs().get(2).getValue().getSound());
}
}
The code is also available on GitHub here and here.
The short answer is because the factory methods are not generated into the #XmlType annotation to tell JAXB to do so:
#XmlRootElement(name = "listOfDogs")
#XmlType(factoryClass=ObjectFactory.class, factoryMethod="createDogs") // not generated
public class Dogs {
Shouldn't it be? Or is ist just a dummy to hold the
#XmlRegistry/#XmlElementDecl declarations?
In my opinion yes it should be used to instantiate the classes.
ObjectFactory is a throw back to JAXB 1.0. In JAXB 1.0 the spec defined what the generated interfaces looked like and implementations could back those generated interfaces with what ever impl they wanted to provide. Back then you needed to use the ObjectFactory class to create your model in a vendor independent way.
JAXB 2.0 switched to a POJO model where you were free to use the default constructor. If JAXB 1.0 had never existed would there be an ObjectFactory class, that's hard to tell. Since it previously existed the ObjectFactory class was kept for a couple of reasons:
It made it easier for people transitioning for people transitioning from JAXB 1.0 to interact with the generated model.
It provided a location to specify the multiple root elements for a class via #XmlElementDecl. The #XmlRegistry annotation is really just a marker annotation used to indicate the class that contains the #XmlElementDecl annotations without restricting it to a class called ObjectFactory.
Your Use Case
Your use case may be able to be achieved with an XmlAdapter, although its not clear to me what logic you are trying to have in the ObjectFactory.
XmlAdapter (DogAdapter)
Your custom logic goes on the XmlAdapter.
import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.*;
public class DogAdapter extends XmlAdapter<JAXBElement<DogType>, JAXBElement<DogType>> {
#Override
public JAXBElement<DogType> unmarshal(JAXBElement<DogType> v) throws Exception {
return new Dog(v.getName().getLocalPart(), v.getValue());
}
#Override
public JAXBElement<DogType> marshal(JAXBElement<DogType> v) throws Exception {
return v;
}
}
Dogs
The XmlAdapter is referenced from the #XmlJavaTypeAdapter annotation.
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name = "listOfDogs")
public class Dogs {
private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();
#XmlElementWrapper(name = "dogs")
#XmlElementRef(name = "dog")
#XmlJavaTypeAdapter(DogAdapter.class)
public List<JAXBElement<DogType>> getDogs() {
return this.dogs;
}
#Override
public String toString() {
return "Dogs [dogs=" + dogs + "]";
}
}
ObjectFactory
ObjectFactory is now a dumb class that just holds the #XmlElementDecl annotations:
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
public Dogs createDogs() {
return new Dogs();
}
#XmlElementDecl(name = "dog")
public JAXBElement<DogType> createDog(DogType value) {
return new Dog(value);
}
#XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public JAXBElement<DogType> createFido(DogType value) {
return new JAXBElement<DogType>(new QName("fido"), DogType.class, value);
}
#XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
public JAXBElement<DogType> createBarks(DogType value) {
return new JAXBElement<DogType>(new QName("barks"), DogType.class, value);
}
}
UPDATE
My question, however is more about the specification. According to the
spec, should the create* methods from the ObjectFactory be executed or
not?
In JAXB 2 there is no difference in a model created from scratch versus one generated from an XML Schema. As such you need to look to the spec at what it says about classes. According to what is reference below it comes down to no-arg constructor or a specified factory method.
From section 8.7.1.2 Mapping of the JAXB 2.2 (JSR-222) specification:
a class must have a public or protected no-arg constructor or a
factory method identified by {factoryClass(), factoryMethod()} unless
it is adapted using #XmlJavaTypeAdapter.
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>
I am refactoring some code to use JAXB and reflection to output code to the client, it is currently using an XMLWriter and manually creating the tags each time.
The problem I am having is that due to constraints on the client side, I need to have empty elements in the XML for any null fields in the java class.
While I realize this problem can be solved by adding nillable=true to each JAXB XmlElement annotation, that is not the most practical, as I have a lot of those annotations.
I was hoping to find a way to set nillable=true as a global attribute (or as the default value). This would also make it easier for future colleagues to work on it, as they won't need to remember that every annotation should include the nillable attribute.
I haven't found much besides descriptions of the default behavior. I find it surprising that no one else has posted a similar question in the past. From what I have found, it doesn't seem to me that there is any built-in support for making the default configurable. Is this something that might be solved with a custom JAXB implementation or maybe a third party JAXB implementation?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
I have entered an enhancement request to have this behaviour added to EclipseLink JAXB (MOXy):
http://bugs.eclipse.org/368547
WORK AROUND
As a work around if all your mapped String fields/properties are mapped to XML elements then the following XmlAdapter approach may work for you:
NullStringAdapter
This XmlAdapter will marshal instances of String as an object called AdaptedString. AdaptedString contains the String value as well as a field mapped to the xsi:nil attribute. In the XmlAdapter we will set the value of that field based on whether or not the String value is null.
package forum8841221;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
public class NullStringAdapter extends XmlAdapter<NullStringAdapter.AdaptedString, String> {
#Override
public AdaptedString marshal(String v) throws Exception {
AdaptedString adaptedString = new AdaptedString();
if(null == v) {
adaptedString.nil = true;
}
adaptedString.value = v;
return adaptedString;
}
#Override
public String unmarshal(AdaptedString v) throws Exception {
return v.value;
}
public static class AdaptedString {
#XmlAttribute(namespace="http://www.w3.org/2001/XMLSchema-instance")
public Boolean nil;
#XmlValue
#XmlJavaTypeAdapter(VoidStringAdapter.class)
public String value;
}
public static class VoidStringAdapter extends XmlAdapter<String, String> {
#Override
public String marshal(String v) throws Exception {
return v;
}
#Override
public String unmarshal(String v) throws Exception {
return v;
}
}
}
package-info
We can register that we want this XmlAdapter to apply to all the mapped String fields/properties on this package by registering the XmlAdapter at the package level.
#XmlJavaTypeAdapter(value=NullStringAdapter.class, type=String.class)
#XmlSchema(xmlns={#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package forum8841221;
import javax.xml.bind.annotation.adapters.*;
import javax.xml.bind.annotation.*;
Root
Below is the domain class I have used for this example. It has several String properties, one of them is annotated with #XmlElement(nillable=true)
package forum8841221;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Root {
private String a;
private String b;
private String c;
private String d;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
#XmlElement(nillable=true)
public String getD() {
return d;
}
public void setD(String d) {
this.d = d;
}
}
Demo
package forum8841221;
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 root = new Root();
root.setB("B");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a xsi:nil="true"/>
<b>B</b>
<c xsi:nil="true"/>
<d xsi:nil="true"/>
</root>
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
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);
}
}