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
Related
When serialising objects to XML and specifying namespaces for properties using
#JacksonXmlRootElement(namespace = "http://...")
Jackson will append or prepend ´wstxns1´ to the namespace. For example, say we have these classes:
VtexSkuAttributeValues.java
#JacksonXmlRootElement(localName = "listStockKeepingUnitName")
public class VtexSkuAttributeValues {
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
#JacksonXmlElementWrapper(useWrapping = false)
private VtexSkuAttributeValue[] stockKeepingUnitFieldNameDTO;
public VtexSkuAttributeValue[] getStockKeepingUnitFieldNameDTO() {
return stockKeepingUnitFieldNameDTO;
}
public void setValues(VtexSkuAttributeValue[] values) {
this.stockKeepingUnitFieldNameDTO = values;
}
}
VtexSkuAttributeValue.java
#JacksonXmlRootElement(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public class VtexSkuAttributeValue {
private String fieldName;
private FieldValues fieldValues;
private int idSku;
public int getIdSku() {
return idSku;
}
public String getFieldName() {
return fieldName;
}
public FieldValues getFieldValues() {
return fieldValues;
}
public void setIdSku(int idSku) {
this.idSku = idSku;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setFieldValues(FieldValues fieldValues) {
this.fieldValues = fieldValues;
}
#JacksonXmlRootElement(localName = "fieldValues", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public static class FieldValues {
#JacksonXmlProperty(namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
#JacksonXmlElementWrapper(useWrapping = false)
public String[] string;
public String[] getString() {
return string;
}
public void setValues(String[] values) {
this.string = values;
}
}
}
I then use the XmlMapper to serialise and get:
<listStockKeepingUnitName>
<wstxns1:StockKeepingUnitFieldNameDTO xmlns:wstxns1="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>talle</fieldName>
<fieldValues>
<wstxns2:string xmlns:wstxns2="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</wstxns2:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns1:StockKeepingUnitFieldNameDTO>
<wstxns3:StockKeepingUnitFieldNameDTO xmlns:wstxns3="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>color</fieldName>
<fieldValues>
<wstxns4:string xmlns:wstxns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6244</wstxns4:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns3:StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
Even though this is valid XML, the web service I'm working with doesn't accept it. I debugged it and it's due to the wstxns properties in the tags that Jackson adds for some reason.
Is there a way to prevent Jackson from adding that to the tags. The only workaround I could come up with is performing a string.replaceAll on the resulting XML but it's obviously not ideal.
To write XML Jackson uses javax.xml.stream.XMLStreamWriter. You can configure instance of that class and define your own prefixes for namespaces and set default one if needed. To do that we need to extend com.fasterxml.jackson.dataformat.xml.XmlFactory class and override a method which creates XMLStreamWriter instance. Example implementation could look like below:
class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter writer = super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
You can use it as below:
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
String defaultNamespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
Map<String, String> otherNamespaces = Collections.singletonMap("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
XmlMapper xmlMapper = new XmlMapper(new NamespaceXmlFactory(defaultNamespace, otherNamespaces));
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(xmlMapper.writeValueAsString(new VtexSkuAttributeValues()));
}
}
In VtexSkuAttributeValues class you can declare:
public static final String DEF_NMS = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
and use it for every class and field where it should be used as default namespace. For example:
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = DEF_NMS)
For properties, for which you do not want to change name you can use:
#JacksonXmlProperty(namespace = VtexSkuAttributeValues.DEF_NMS)
Above code prints for some random data:
<listStockKeepingUnitName>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
If it is not what you want you can play with that code and try other methods which are available for you to configure this instance.
To create this example Jackson in version 2.9.9 was used.
This seems to be the missing piece. It allows you to set the prefix and namespace.
static class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter2 writer = (XMLStreamWriter2)super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
writer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
The only remaining issue I have is
#JacksonXmlProperty(localName = "#xsi.type", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
#JsonProperty("#xsi.type")
private String type;
Creates the following output:
Still trying to resolve how to make it be xsi:type="networkObjectGroupDTO" instead.
i create a mapper with
new ObjectMApper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.setSerializationInclusion(Include.NON_NULL)
and serialization works perfectly on fields (no getters and setters). field currentStatus is serialized to "currentStatus" (first letter uppercase). but i have also one getter (without a field and setter) which must be camelCase. so i do:
#JsonProperty("abcDef")
public String getZxy() {...
but it is serialized to "AbcDef" instead of "abcDef". it looks like naming strategy still triggers and change the first letter. i use jackson-databind 2.3.2;
how can i map this getter with first letter lowercase?
EDIT:
ugly code, but shows the problem. this test should pass but it fails
import static org.assertj.core.api.Assertions.assertThat;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
public class JsonFailureTest {
#Test
public void should_serialize_first_letter_lowercase() throws Exception {
String json = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.writeValueAsString(
new Object(){
#JsonProperty("fooBar")
public String whatever() {return "";}
});
assertThat(json).contains("fooBar");
}
}
Here's a workaround using a custom "annotation-aware" strategy:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public class Foo {
public static void main(final String[] args) throws JsonProcessingException {
final SomeObject someObject = new SomeObject();
someObject.setZxy("foobar");
final ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy() {
#Override
public String nameForGetterMethod(final MapperConfig<?> config, final AnnotatedMethod method, final String defaultName) {
final JsonProperty annotation = method.getAnnotation(JsonProperty.class);
if (annotation != null) {
return annotation.value();
}
return super.nameForGetterMethod(config, method, defaultName);
}
});
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
System.out.println(mapper.writeValueAsString(someObject));
}
private static class SomeObject {
private String zxy;
#JsonProperty("abcDef")
public String getZxy() {
return this.zxy;
}
public void setZxy(final String zxy) {
this.zxy = zxy;
}
}
}
Output:
{"abcDef":"foobar"}
I have one sub-tree that I would like to append on an object and make JAXB marshall the all thing as a single tree (and with appropriate tags). But currently, the root tag of the sub-tree is replaced by the tag of another object
Unfortunately, I am not allowed to publish the original code here, so I reproduced my problem in a test code (so bear with me if you find this dumb).
The idea is that I would like to output the following structure:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root xmlns:ns2="urn:my:foo:bar:1.0" xmlns:ns3="urn:other:foo:bar:1.1">
<Content>
<Header>
<ns3:Leaf/>
</Header>
</Content>
</ns2:Root>
but currently, all I get is this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root xmlns:ns2="urn:my:foo:bar:1.0" xmlns:ns3="urn:other:foo:bar:1.1">
<Content>
<Header/>
</Content>
</ns2:Root>
I have two XSD's to generate all the necessary classes, so I am ok on that side (but since those classes are generated, I cannot modify them).
Here is a sample code that produces the second XML (the wrong one):
package foo.bar;
import java.io.OutputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class Test {
private JAXBContext context;
public Test() throws JAXBException {
context = JAXBContext.newInstance(RootElement.class, LeafElement.class);
}
#XmlRootElement(name = "Root", namespace = "urn:my:foo:bar:1.0")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Root", propOrder = { "content" })
public static class RootElement {
#XmlElement(name = "Content")
protected ContentElement content;
public ContentElement getContent() {
return content;
}
public void setContent(ContentElement content) {
this.content = content;
}
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Content", propOrder = { "dummy" })
public static class ContentElement {
#XmlElement(name = "Header")
protected Object dummy;
public Object getDummy() {
return dummy;
}
public void setDummy(Object dummy) {
this.dummy = dummy;
}
}
#XmlRootElement(name = "Leaf", namespace = "urn:other:foo:bar:1.1")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Leaf")
public static class LeafElement {
}
public Node marshal(Object obj) throws JAXBException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = null;
try {
DocumentBuilder db = dbf.newDocumentBuilder();
doc = db.newDocument();
} catch (ParserConfigurationException ex) {
throw new JAXBException(ex);
}
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(obj, doc);
return doc.getDocumentElement();
}
public void marshal(Object obj, OutputStream stream) throws JAXBException {
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(obj, stream);
}
public void test() throws JAXBException {
RootElement root = new RootElement();
ContentElement content = new ContentElement();
root.setContent(content);
LeafElement leaf = new LeafElement();
content.setDummy(marshal(leaf));
marshal(root, System.out);
}
public static void main(String[] args) throws JAXBException {
new Test().test();
}
}
In that code you find 3 "marshallable" classes:
RootElement,
ContentElement and
LeafElement.
The first two classes come from one XSD (with a given namespace) and the last one comes from another XSD (with another namespace), as illustrated in the sample code.
So far, all I found to fix this, was to create an additional class that would be set as dummy on the ContentElement and would itself hold the LeafElement, so that JAXB creates the appropriate intermdiate Node. But I find this solution quite ugly, not really maintainable and was hoping that JAXB had some way to handle such cases.
If you need more info, or you need me to re-formulate my question, don't hesitate. I am having a hard time to explain my problem with simple words.
Constraints are the following:
I cannot modify RootElement, ContentElement nor LeafElement
I cannot use something else than JAXB
If you cannot change any element class, then you must create a holder object to leaf.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class LeafElementHolder {
#XmlAnyElement
private Object leaf;
public Object getLeaf() {
return leaf;
}
public void setLeaf(Object leaf) {
this.leaf = leaf;
}
}
Add this class to context
public Test() throws JAXBException {
context = JAXBContext.newInstance(RootElement.class, LeafElement.class, LeafElementHolder.class);
}
and use this in your test() method
LeafElement leaf = new LeafElement();
LeafElementHolder holder = new LeafElementHolder();
holder.setLeaf(leaf);
content.setDummy(marshal(holder));
You have 4 elements in XML so you must have 4 classes in Java.
ns2:Root
ns2:Content
ns2:Header
ns3:Leaf
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
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);
}
}