XmlAdapter not working as expected in JAXB RI - java

I am trying to implement a XmlAdapter for modifying the marshalling/unmarshalling of certain object properties. Particularly, I tried with the NullStringAdapter described here:
Jaxb: xs:attribute null values
The objective of the NullStringAdapter is marshalling null values as empty strings, and viceversa.
The only difference with the example described above and my code, is that I want to apply the adapter to an element, not to an attribute, so what I have is:
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue; //someValue could be null, in that case the adapter should marshall it as an empty string
}
However, after some debugging, I realized that the Adapter methods are never called during the marshalling from Java to XML!. This occurs when the XmlElement value is null.
When this value is different than null, the adapter methods are called as expected.
Thanks for any help!.

Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2 (JSR-222) expert group.
However, after some debugging, I realized that the Adapter methods are
never called during the marshalling from Java to XML!. This occurs
when the XmlElement value is null. When this value is different than
null, the adapter methods are called as expected.
This behaviour varies between implementations of JAXB. The JAXB reference implementation will not call the marshal method on the XmlAdapter when the field/property is null, but MOXy will.
What the JAXB spec says (section 5.5.1 Simple Property)
The get or is method returns the property’s value as specified in the
previous subsection. If null is returned, the property is considered
to be absent from the XML content that it represents.
The MOXy interpretation of this statement is that the value of the field/property is really the value once it has gone through the XmlAdapter. This is necessary to support the behaviour that Sergio is looking for.

Of course the adapter will never be called if there isn't any element in the input to trigger that action. What happens in that example you're linked is that an attribute with an empty value is presented:
<element att="" />
The key here is that there is an att attribute, but it has an empty String. So a JAXB unmarshaller is gonna present that to the setter. But, since there's an adapter declared on it, it will pass through there and get turned into a null value.
But if you had this
<element />
it's another story. There's no att attribute, so the setter would never need to be called.
There's a difference between an element that occurs but has no content and a complete absence of an element. The former can basically be considered to contain an empty String, but the latter is just "not there".
EDIT: tested with these classes...
Bean.java
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name="Test")
public class Bean {
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
private String someValue;
public Bean() {
}
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
NullStringAdapter.java
package jaxbadapter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class NullStringAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(final String v) throws Exception {
if("".equals(v)) {
return null;
}
return v;
}
#Override
public String marshal(final String v) throws Exception {
if(null == v) {
return "";
}
return v;
}
}
ObjectFactory.java
package jaxbadapter;
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 {
public ObjectFactory() {
}
public Bean createBean() {
return new Bean();
}
#XmlElementDecl(name = "Test")
public JAXBElement<Bean> createTest(Bean value) {
return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
}
}
Main.java
package jaxbadapter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;
public class Main {
public static void main(String[] args) throws Exception {
final JAXBContext context = JAXBContext.newInstance("jaxbadapter");
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
final ObjectFactory of = new ObjectFactory();
final Bean b1 = new Bean();
final Bean b2 = new Bean();
b2.setSomeValue(null);
final Bean b3 = new Bean();
b3.setSomeValue("");
m.marshal(of.createTest(b1), System.out);
System.out.println("");
m.marshal(of.createTest(b2), System.out);
System.out.println("");
m.marshal(of.createTest(b3), System.out);
System.out.println("");
}
}
This is the output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
<someValue></someValue>
</Test>
Actually surprised me quite a bit. I've then tried changing the getter to return someValue == null ? "" : someValue; to no avail. Then set a breakpoint on the getter and found out it never gets called.
Apparently JAXB uses reflection to try and retrieve the value rather than going through the setter when using XmlAccessType.FIELD. Hardcore. Now, you can bypass this by using XmlAccessType.PROPERTY instead and annotating either the getter or setter...
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
#XmlElement
#XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
... but that still didn't help. The adapter's marshal method was only called once, on the last test case where an empty String had been set. Apparently it first calls the getter and when that returns null, it simply skips the adapter stuff altogether.
The only solution I can come up with is just foregoing the use of an adapter altogether here and put the substitution in the getter, making sure to use XmlAccessType.PROPERTY:
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
#XmlElement
// #XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue == null ? "" : someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
That worked for me. It's only really an option if you're creating the JAXB-annotated classes yourself and not generating them via XJC from a schema, though.
Maybe someone can clarify a bit why adapters are skipped for nulls and if there's a way to change that behaviour.

Related

override #XmlElement annotation with FIELD accessor type

I have a class with #XmlAccessorType(XmlAccessType.FIELD) annotation, and each private and protected field is annotated with #XmlElement(name='...').
The challenge: I may want to rename one of the xml element names in a later stage. This leads me to the question. Is there a way to override/redefine these annotations, if I create a sub-class ?
I believe that some implementations of JaxB allow for XML configuration to override the annotations. In this case this may actually be possible. Here is an article from Eclipslink explaining how this can be done http://www.eclipse.org/eclipselink/documentation/2.4/solutions/jpatoxml004.htm
In my opinion you can just build an XML configuration for the JaxB file you want to override.
I tried first with the #XmlAccessorType(XmlAccessType.FIELD) and to hide with #XmlTransient. This only works, if you mark the field in the superclass and in the child class with #XmlTransient. But I assume, this is not what you want.
As second approach I've tried with more restrictive #XmlAccessorType(XmlAccessType.PROPERTY) in the superclass and #XmlAccessorType(XmlAccessType.NONE) in the child class. See here my example:
package com.so.example;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/myresource")
public class MyResource {
#GET
#Path("/car")
#Produces(MediaType.APPLICATION_XML)
public Car getCar() {
Car car = new Car();
car.setWheels(4);
return car;
}
#GET
#Path("/suv")
#Produces(MediaType.APPLICATION_XML)
public Suv getSuv() {
Suv suv = new Suv();
List<String> bigWheels = new ArrayList<>();
bigWheels.add("left front wheel");
bigWheels.add("right front wheel");
bigWheels.add("left rear wheel");
bigWheels.add("right rear wheel");
suv.setBigWheels(bigWheels);
return suv;
}
}
Class Car:
package com.so.example;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement
public class Car {
protected Integer wheels;
public Car() {
}
#XmlElement(name = "wheels", nillable = true)
public Integer getWheels() {
return wheels;
}
public void setWheels(Integer wheels) {
this.wheels = wheels;
}
}
Class Suv (Child):
package com.so.example;
import java.util.List;
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.XmlTransient;
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
public class Suv extends Car {
#XmlTransient
private Integer wheels;
private List<String> bigWheels;
public Suv() {
}
#Override
#XmlTransient
public Integer getWheels() {
return wheels;
}
#Override
public void setWheels(Integer wheels) {
this.wheels = wheels;
}
#XmlElement
public List<String> getBigWheels() {
return bigWheels;
}
public void setBigWheels(List<String> bigWheels) {
this.bigWheels = bigWheels;
}
}
One way to "hide" the element wheels of the superclass would be to mark it as "nillable=true" and not use primitive types. In this case, the field wheels will be marshalled to <wheels xsi:nil="true"/>
If it's possible for you to not use the parent class for marshalling and you are only using child classes, you could use the approach described here:
http://blog.bdoughan.com/2011/06/ignoring-inheritance-with-xmltransient.html
Also you could use moxy and specify a custom binding:
http://www.eclipse.org/eclipselink/documentation/2.4/moxy/runtime003.htm
Whilst in java, to my knowledge, overriding an annotation #XmlElement(name='...') to change the name property is not possible; you can create a global variable in your code and either pass it through your classes or your functions following the #XmlElement(name='...').
In the code below I created a single class but it contains the setter and getter methods required if you want to pass it through to another class
#XMLAccessorType(XMLAccessType.FIELD)
public class YourClass {
#XmlTransient
private String string = ""; //This can be replaced with whatever variable you are manipulating
//That could be an int or a file or anything really
#XmlElement(name = "your_name")
private void doSomething() {
String temp = getString(); //This variable is normally used to pass between different
//classes but may as well use it if you have one
//Your code which manipulates the String
setString(temp); //This variable is normally used to pass between different classes but
//may as well use it if you have one
}
#XmlElement(name = "your_other_name")
private void doSomethingElse() {
String temp = getString();
//Your code which manipulates the String
setString(temp);
}
public void getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
I would reccomend looking at the Java Docs for #XmlTransient and these two other relevant SO questions.
How to override JAXB #XMLAccessorType(XMLAccessType.FIELD) specified at a Class level with #XMLElement on a getter method for a property?
Jaxb - Overriding the XMLElement name attribute

Unmarshalling a simple XML

I am having hard time unmarshalling the following XML using JAXB. This is the xml which is having only one field with an attribute. I have referred many tutorials where they only do an example of reading a string in fields which s not helpful in my case.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CircuitImpactConfigs id="1">
<Objects>
<Object type="1" impactAnalysisDataBuilderClassName="tttttttt"/>
<Object type="2" impactAnalysisDataBuilderClassName="yyyyyyyyy"/>
<Object type="3" impactAnalysisDataBuilderClassName="eeeeeee" />
<Object type="4" impactAnalysisDataBuilderClassName="iiiiiiiii"/>
<Object type="5" impactAnalysisDataBuilderClassName="rrrrrrrrrr"/>
<Object type="6" impactAnalysisDataBuilderClassName="zzzzzz"/>
<Object type="7" impactAnalysisDataBuilderClassName="qqqqqqqqqqq"/>
</Objects>
<ForceSwitchMode name="FORCE_SWITCHED" />
</CircuitImpactConfigs>
Based on what i learnt from tutorial
My Classes are CircuitImpactConfigs.java
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "CircuitImpactConfigs")
#XmlAccessorType(XmlAccessType.FIELD)
public class CircuitImpactConfigs {
private ForceSwitchMode ForceSwitchMode;
List<Obj> Object;
#XmlElement
public List<Obj> getObject() {
return Object;
}
public void setObject(List<Obj> object) {
Object = object;
}
#XmlElement
public ForceSwitchMode getForceSwitchMode() {
return ForceSwitchMode;
}
public void setForceSwitchMode(ForceSwitchMode forceSwitchMode) {
ForceSwitchMode = forceSwitchMode;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class ForceSwitchMode {
private String name;
#XmlAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class Obj {
String type;
String impactAnalysisDataBuilderClassName;
#XmlAttribute
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#XmlAttribute
public String getImpactAnalysisDataBuilderClassName() {
return impactAnalysisDataBuilderClassName;
}
public void setImpactAnalysisDataBuilderClassName(String impactAnalysisDataBuilderClassName) {
this.impactAnalysisDataBuilderClassName = impactAnalysisDataBuilderClassName;
}
}
I am getting null list when doing the unmarshalling. This is the class where i create the JAXBcontext object and create unmarshalling object.
import java.io.File;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class CircuitImpactConfUnmarshaller {
public static void main(String[] args) throws JAXBException {
File file = new File("CircuitImpact.xml");
System.out.println(file.exists());
JAXBContext jaxbContext = JAXBContext.newInstance(CircuitImpactConfigs.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
CircuitImpactConfigs que = (CircuitImpactConfigs) jaxbUnmarshaller.unmarshal(file);
System.out.println(que.getForceSwitchMode().getName());
List<Obj> list = que.getObject();
System.out.println(list);
}
}
the last print statement is giving null. I understand i am doing something wrong in the class Obj
JAXB uses implicit naming conventions and explicit annotations to define a mapping between a XML and a Java structure.
Either element and attribute names in the XML match field names in Java (match by naming convention) or you need to use annotations to establish the mapping.
The Java list CircuitImpactConfigs.Object is not getting filled because the mapping failed, since the corresponding element in the XML is named Objects.
You can now either rename CircuitImpactConfigs.Object to CircuitImpactConfigs.Objects or use the name parameter of a JAXB annotation to define the corresponding name:
#XmlElement(name="Objects")
public List<Obj> getObject() {
EDIT: As you indicate in your comments there are still other mapping issues with your code. I would suggest that you adapt another approach:
Create a CircuitImpactConfigs object with all subobjects filled.
Marhsall that object to a XML file.
Check that the XML is in the expected format. If not, tweak the mapping.
Following this approach you can be sure that a XML file in the desired format can be unmarshalled to your Java structure. The code to marshall is
CircuitImpactConfigs que = ...
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(que, System.out);

Jaxb marshalling with custom annotations

I have a requirement, to marshall/unmarshall some elements of java pojo depending upon a custom annotation marked on the field.
suppose there are 3 fields in my java pojp
#CustomVersion("v1")
private String field1;
#CustomVersion("v1","v2")
private String field2;
#CustomVersion("v2")
private String field3;
i would like to marshall only the fields with v1 if i pass version="v1" parameter while conversion in jaxb. if i pass v2, all fields with v2 annotation should only be marshalled.
is that even possible using jaxb? i am sure selective marshalling would be supported through some library or way, am not still able to figure it out after quite some searching.
any help or advice or pointers are highly appreciated.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is an example of how you could use MOXy's #XmlNamedObjectGraphs extension to map your use case.
Java Model
Foo
The #XmlNamedObjectGraphs extension allows you to specify multiple subsets of mappings identified by a key.
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlNamedAttributeNode;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraph;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraphs;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlNamedObjectGraphs({
#XmlNamedObjectGraph(
name="v1",
attributeNodes = {
#XmlNamedAttributeNode("field1"),
#XmlNamedAttributeNode("field2")}),
#XmlNamedObjectGraph(
name="v2",
attributeNodes = {
#XmlNamedAttributeNode("field2"),
#XmlNamedAttributeNode("field3")})
})
public class Foo {
private String field1 = "ONE";
private String field2 = "TWO";
private String field3 = "THREE";
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties 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 Code
Demo
You can specify the key corresponding to the object graph to have that subset applied to the object you are marshalling.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.eclipse.persistence.jaxb.MarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Foo foo = new Foo();
// Marshal Everything
marshaller.marshal(foo, System.out);
// Marshal "v1" Data
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "v1");
marshaller.marshal(foo, System.out);
// Marshal "v2" Data
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "v2");
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field1>ONE</field1>
<field2>TWO</field2>
<field3>THREE</field3>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field1>ONE</field1>
<field2>TWO</field2>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field2>TWO</field2>
<field3>THREE</field3>
</foo>
For More Information
http://blog.bdoughan.com/2013/03/moxys-object-graphs-inputoutput-partial.html
First of all I would suggest doing such preprocessing before marshalling. It would be much easier. However if it is not possible for some reason then you can create you custom type adapter. Then you can put #XmlJavaTypeAdapter(VersioningAdapter.class) on every type that you want to have versioning enabled.
#XmlJavaTypeAdapter can also be specified on package level, but you have to specify to which types it applies. You cannot use XmlAdapter without specifying somewhere #XmlJavaTypeAdapter.
Drawbacks of such solution:
if you have multiple versioned types then each of them has to be annotated with #XmlJavaTypeAdapter
#XmlJavaTypeAdapter does not work for root element, only on child elements. You have to call adapter manually on root element before marshalling
AFAIK there is no other option for customizing JAXB marshalling. That's why I think that annotation processing should be performed in separate step before marshalling. Unless you can accept mentioned limitations.
Sample adapter (full code can be found here):
public class VersioningAdapter extends XmlAdapter<Object, Object> {
#Override
public Object unmarshal(Object v) throws Exception {
// TODO Auto-generated method stub
return null;
}
#Override
public Object marshal(Object v) throws Exception {
if (v == null) {
return v;
}
Field[] fields = v.getClass().getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getDeclaredAnnotations();
CustomVersion annotation = findCustomVersion(annotations);
if (annotation != null) {
if (!contains(annotation, Configuration.getVersion())) {
field.setAccessible(true);
field.set(v, null);
}
}
}
return v;
}
private CustomVersion findCustomVersion(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation instanceof CustomVersion) {
return (CustomVersion) annotation;
}
}
return null;
}
private boolean contains(CustomVersion annotation, String version) {
String[] values = annotation.value();
for (String value : values) {
if (value.equals(version)) {
return true;
}
}
return false;
}
}

mapping two xmls with different rootelement name to the same java object

I have
xml1:
<abc><name>hello</name></abc>
xml2
<xyz><name>hello</name></xyz>
I have one java class.
#XmlRootElement(name="abc") (this
public class Foo{
#XmlElement
String name;
}
I do not want another class, but would like to accomodate xml2 with the Foo class itself.
I'm okay to intercept or modify it during pre-marshalling/pre-unmarshalling.
Thanks!
}
Depending on exactly what you mean by "I don't want another class", maybe this will work out for you:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import java.io.StringReader;
public class JaxbBindTwoRootElementsToSameClass {
public static void main(String[] args) throws Exception {
String xml1 = "<abc><name>hello</name></abc>";
String xml2 = "<xyz><name>hello</name></xyz>";
Unmarshaller unmarshaller = JAXBContext.newInstance(Foo.class).createUnmarshaller();
Object o1 = unmarshaller.unmarshal(new StringReader(xml1));
Object o2 = unmarshaller.unmarshal(new StringReader(xml2));
System.out.println(o1);
System.out.println(o2);
}
#XmlSeeAlso({Foo.Foo_1.class, Foo.Foo_2.class})
static class Foo {
#XmlRootElement(name = "abc")
static class Foo_1 extends Foo {}
#XmlRootElement(name = "xyz")
static class Foo_2 extends Foo {}
#XmlElement
String name;
#Override
public String toString() {
return "Foo{name='" + name + '\'' + '}';
}
}
}
Output:
Foo{name='hello'}
Foo{name='hello'}
It has the benefit of using JAXB almost exactly the way you usually would. It's just a slightly unconventional class organization. You even only have to pass Foo.class to the JAXBContext when you create it. No tinkering with JAXB internals needed.
Unmarshalling
You could use the unmarshal methods that take a class parameter. When a class is specified the JAXB implementation does not need to use the root element to determine the class to unmarshal to.
Marshalling
When marshaling you can wrap the root object in a JAXBElement to provide the root element information.

How to add XML processing instructions during JAXB marshal

I would like to add a processing instruction whenever a collection/array property is serialized to get something like
<alice>
<? array bob ?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Is this possible with JAXB? Or at least with some specific JAXB implementation?
You could leverage an XMLStreamWriter and an XmlAdapter to do this:
BobAdapter
Things to note about the XmlAdapter:
It's stateful and references an XMLStreamWriter. We will later leverage JAXB's ability to set a stateful XmlAdapter on a Marshaller.
It converts from a List<String> to a List<String>, we're just using an XmlAdapter here to get an event.
The marshal method is where we call writeProcessingInstruction on the XMLStreamWriter:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLStreamWriter;
public class BobAdapter extends XmlAdapter<List<String>, List<String>> {
private boolean first = true;
private XMLStreamWriter xmlStreamWriter;
public BobAdapter() {
}
public BobAdapter(XMLStreamWriter xmlStreamWriter) {
this();
this.xmlStreamWriter = xmlStreamWriter;
}
#Override
public List<String> marshal(List<String> stringList) throws Exception {
if(first) {
xmlStreamWriter.writeProcessingInstruction("array", "bob");
first = false;
}
return stringList;
}
#Override
public List<String> unmarshal(List<String> stringList) throws Exception {
return stringList;
}
}
Alice
The #XmlJavaTypeAdapter annotation is used to link the XmlAdapter with the field/property:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Alice {
private List<String> bob;
#XmlJavaTypeAdapter(BobAdapter.class)
public List<String> getBob() {
return bob;
}
public void setBob(List<String> bob) {
this.bob = bob;
}
}
Demo
package forum6931520;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Alice.class);
File xml = new File("src/forum6931520/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Alice alice = (Alice) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
XMLOutputFactory xof = XMLOutputFactory.newFactory();
XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(System.out);
marshaller.setAdapter(new BobAdapter(xmlStreamWriter));
marshaller.marshal(alice, xmlStreamWriter);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<alice>
<?array bob?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Output
<?xml version='1.0' encoding='UTF-8'?><alice><?array bob?><bob>edgar</bob><bob>david</bob></alice>
Note
This example needs to be run with EclipseLink JAXB (MOXy) as the JAXB RI will throw the following exception (I'm the MOXy lead):
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
java.util.List does not have a no-arg default constructor.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
For More Information
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
UPDATE
I have entered an enhancement request (https://bugs.eclipse.org/354286) to add native support for processing instructions. This would eventually allow you to specify the following on your property:
#XmlProcessingInstruction(target="array", value="bob")
public List<String> getBob() {
return bob;
}

Categories

Resources