EclipseLink and Spring OXM Inheritance issues - java

I am stuck with the inheritance problem while trying to unmarshal to object. Here is my class
A
#XmlRootElement(name="A")
public abstract class A{
}
B
#XmlRootElement(name="B")
public class B extends A{
String bField;
#XmlAttribute(name="b")
public String getBField(){
return bField;
}
public void setBField(String value){
this.bField = value;
}
}
C
#XmlRootElement(name="C")
public class C extends A{
String cField;
#XmlAttribute(name="c")
public String getCField(){
return cField;
}
public void setCField(String value){
this.cField = value;
}
}
Container
#XmlRootElement(name="container")
public class Container{
ArrayList<B> listB;
ArrayList<C> listC;
public ArrayList<B> getListB(){
return listB;
}
#XmlElementWrapper(name="list-B")
#XmlElement(name="b")
public ArrayList<B> getListB(){
return listB;
}
#XmlElementWrapper(name="list-C")
#XmlElement(name="c")
public ArrayList<C> getListC(){
return listC;
}
public ArrayList<C> getListC(){
return listC;
}
}
Then the input XML file
<container>
<list-B>
<b b="BFied"/>
</list-B>
<list-C>
<c c="CField"/>
</list-C>
</container>
I used EclipseLink JAXB integrated with Spring OXM. When i unmarshal xml file to an instance of Container, every thing is duplicated. In list B i have 2 B instances which duplicated ( the same thing with list C).
Please let me know where did i do wrong? Thank you!

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I haven't been able to reproduce the issue that you are seeing. I am using the EclipseLink 2.4.0 which can be obtained from the following location:
http://www.eclipse.org/eclipselink/downloads/
Below is my complete code based on your question:
A
package forum11642669;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="A")
public abstract class A{
}
B
package forum11642669;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "B")
public class B extends A {
String bField;
#XmlAttribute(name = "b")
public String getBField() {
return bField;
}
public void setBField(String value) {
this.bField = value;
}
}
C
package forum11642669;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "C")
public class C extends A {
String cField;
#XmlAttribute(name = "c")
public String getCField() {
return cField;
}
public void setCField(String value) {
this.cField = value;
}
}
Container
The version of the Container class you had in your question wouldn't compile, so I've modified it below:
package forum11642669;
import java.util.ArrayList;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "container")
public class Container {
ArrayList<B> listB;
ArrayList<C> listC;
#XmlElementWrapper(name = "list-B")
#XmlElement(name = "b")
public ArrayList<B> getListB() {
return listB;
}
public void setListB(ArrayList<B> listB) {
this.listB = listB;
}
#XmlElementWrapper(name = "list-C")
#XmlElement(name = "c")
public ArrayList<C> getListC() {
return listC;
}
public void setListC(ArrayList<C> listC) {
this.listC = listC;
}
}
jaxb.properties
To specify MOXy as your JAXB 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
package forum11642669;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Container.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11642669/input.xml");
Container container = (Container) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(container, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<container>
<list-B>
<b b="BFied"/>
</list-B>
<list-C>
<c c="CField"/>
</list-C>
</container>

Related

JAXB adding attribute to collection element [duplicate]

This question already has answers here:
How can I add xml attributes to jaxb annotated class XmlElementWrapper?
(3 answers)
Closed 3 years ago.
I have a class which looks like this
#XmlRootElement(name="root")
public class MyClass {
#XmlElementWrapper(name="list")
#XmlElement(name="item")
private List<String> myList = new ArrayList<String>();
// getters, setters
}
I want to add an attribute a to list element to reach following XML
<root>
<list a="1">
<item>a</item>
<item>b</item>
...
</list>
</root>
You could create another class for this list as following:-
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="list")
public class RootList {
private String a;
private List<String> someList;
#XmlAttribute(name="a")
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
#XmlElement(name="item")
public List<String> getSomeList() {
return someList;
}
public void setSomeList(List<String> someList) {
this.someList = someList;
}
public RootList(String numValue,List<String> someListValue) {
this();
this.a = numValue;
this.someList = someListValue;
}
public RootList() {
// TODO Auto-generated constructor stub
}
}
To run the above code using JAXB, use the following:
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.*;
public class Test {
public static void main(String[] args) throws Exception {
List<String> arg = new ArrayList<String>();
arg.add("a");
arg.add("b");
RootList root = new RootList("1", arg);
JAXBContext jc = JAXBContext.newInstance(RootList.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(root, System.out);
}
}
It will generate the following XML as the output:
<list a="1">
<item>a</item>
<item>b</item>
</list>

JAXB Unmarshalling derived types

Based on the following program, it prints out what we expect it to correctly.
Is it possible for such a program to unmarshall correctly if the classes ClassA and ClassB used
the same XmlRootElement name? For example, if they were both defined as "typeA"...? Would it be
possible to do something like that with JAXB?
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Date;
public class JaxbABCTest {
public static void main(String[] args) throws JAXBException, UnsupportedEncodingException {
final JAXBContext context = JAXBContext.newInstance(ABC.class);
final Marshaller marshaller = context.createMarshaller();
final Unmarshaller unmarshaller = context.createUnmarshaller();
ABC class1 = new ClassA();
ABC class2 = new ClassB();
final ByteArrayOutputStream baosA = new ByteArrayOutputStream();
final ByteArrayOutputStream baosB = new ByteArrayOutputStream();
// Marshall to XML
marshaller.marshal(class1, baosA);
marshaller.marshal(class2, baosB);
String xmlA = baosA.toString(Charset.defaultCharset().name());
String xmlB = baosB.toString(Charset.defaultCharset().name());
System.out.println(xmlA);
System.out.println(xmlB);
// Now attempt the reverse.
Object unmarshalA = unmarshaller.unmarshal(new StringReader(xmlA));
Object unmarshalB = unmarshaller.unmarshal(new StringReader(xmlB));
System.out.println(unmarshalA.getClass());
System.out.println(unmarshalB.getClass());
}
}
#XmlTransient
#XmlSeeAlso({
ClassA.class,
ClassB.class
})
abstract class ABC {
private int a;
#XmlAttribute
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
#XmlRootElement(name = "typeA")
class ClassA extends ABC {
private String b;
private Date c;
#XmlAttribute
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
#XmlAttribute
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
}
#XmlRootElement(name = "typeB")
class ClassB extends ABC {
private String b;
private Date c;
private boolean d;
private float e;
#XmlAttribute
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
#XmlAttribute
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
#XmlAttribute
public boolean isD() {
return d;
}
public void setD(boolean d) {
this.d = d;
}
#XmlAttribute
public float getE() {
return e;
}
public void setE(float e) {
this.e = e;
}
}
For straightforward JAXB unmarshalling, (root) one element name refers to an element with a specific structure which is mapped to one certain Java class.
You can, of course, define a class containing the union of the fields of A and B - as long as fields occuring in both classes match. You'll have to have some attribute that lets you decide which of the two "subclasses" it really is. (This is similar in spirit to using an XPath expression that includes a test for the presence (or even value) of an attribute or element.)
You may also pursue a more elaborate approach leaving this element unmarshalled after reading the XML, investigate the DOM tree and create a JAXBContext according to what has been detected. This will let you use the same element name with Java types exactly matching the XML content. Of course, an unambiguous criterion must be available, and youÄll have to write the code for analysing based on te raw DOM tree data.

JAXB marshalling and inheritance

I have 2 classes, one extends the other. The superclass marshals correctly, but the subclass, which adds one attribute, does not. The extra attribute is not present in the XML.
Superclass:
#XmlRootElement()
#XmlAccessorType(XmlAccessType.NONE)
public class SessionRecord extends Record {
SimpleDateFormat hhmm = new SimpleDateFormat("HH:mm");
SimpleDateFormat day = new SimpleDateFormat("EEEEE");
#XmlAttribute protected int sessionId;
#XmlAttribute protected boolean open;
#XmlAttribute protected boolean night;
protected Date start;
protected Date finish;
protected boolean setup;
protected boolean takedown;
#XmlAttribute
public String getDescription() {
if (start==null) start = new Date();
if (finish==null) finish = new Date();
return day.format(start)+(night ? " Night " : " ")+hhmm.format(start)+"-"+hhmm.format(finish)+" "+type();
}
private String type() {
return setup ? "Setup" : (open ? "Open" : (takedown ? "Takedown" : ""));
}
#XmlAttribute
public boolean isSetupTakedown() {
return setup || takedown;
}
}
This produces XML elements similar to this:
<sessionRecord setupTakedown="true" description="Saturday 09:00-13:00 Setup" night="false" open="false" sessionId="0"/>
which is OK.
But the subclass:
#XmlRootElement()
public class VolunteerSession extends SessionRecord {
#XmlAttribute private boolean available;
}
Produces identical output, the available attribute is not marshalled. Why is JAXB not marshalling the extra attribute?
EDIT
further information:
Record superclass is merely this:
public abstract class Record {}
Here is the class representing the top-level document element. It contains lists of Records:
#XmlRootElement(name="response")
#XmlSeeAlso({
RecordList.class,
VolunteerAssignment.class,
VolunteerRecord.class,
SessionRecord.class,
VolunteerSession.class,
VolunteerArea.class,
PossibleAssignment.class})
public class XMLResponse {
#XmlAttribute private String errorMessage;
private List<RecordList<? extends Record>> recordLists = new ArrayList<RecordList<? extends Record>>();
//snip...
public void setError(String errorMessage) {
this.errorMessage = errorMessage;
}
#XmlMixed
public List<RecordList<? extends Record>> getRecordLists() {
return recordLists;
}
}
and finally, RecordList
#XmlRootElement()
public class RecordList<T extends Record> {
#XmlAttribute private String name;
#XmlAttribute private int total;
#XmlAttribute private int count;
#XmlAttribute private int start;
#XmlAttribute private boolean update;
private List<T> records;
// snip constructors, setters
#XmlMixed
public List<T> getRecords() {
return records;
}
}
It sounds as though the VolunteerSession class is not being included in the JAXBContext. This can happen depending on how you created your JAXBContext. Below is some example code where the same object is marshalled based on 3 different instances of JAXBContext each bootstrapped off a different class.
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
VolunteerSession volunteerSession = new VolunteerSession();
marshal(VolunteerSession.class, volunteerSession);
marshal(SessionRecord.class, volunteerSession);
marshal(XMLResponse.class, volunteerSession);
}
private static void marshal(Class bootstrapClass, Object object) throws Exception {
System.out.println(bootstrapClass.getName());
JAXBContext jc = JAXBContext.newInstance(bootstrapClass);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(object, System.out);
System.out.println();
}
}
Output
When the JAXBContext is bootstrapped off of VolunteerSession obviously it has the necessary information.
When the JAXBContext is bootstraped off of the super class SessionRecord it doesn't pull in VolunteerSession. JAXB will automatically process metadata for super classes, but not subclasses. #XmlSeeAlso is usually used in this case to reference mapped subclasses.
VolunteerRecord contains an #XmlSeeAlso annotation that references VolunteerSession. Therefore VolunteerSession is processed as part of the JAXBContext and contains the necessary information when marshalled.
forum20908213.VolunteerSession
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<volunteerSession available="false" sessionId="0" open="false" night="false" description="Sunday 05:53-05:53 " setupTakedown="false"/>
forum20908213.SessionRecord
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sessionRecord sessionId="0" open="false" night="false" description="Sunday 05:53-05:53 " setupTakedown="false"/>
forum20908213.XMLResponse
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<volunteerSession available="false" sessionId="0" open="false" night="false" description="Sunday 05:53-05:53 " setupTakedown="false"/>
You have to list all of your subclasses in #XmlSeeAlso annotation of your parent class.

Unexpected element (uri:"", local: "element"). Expected elements are <{}link> <{}size>

When trying to unmarshall this xml:
<holder>
<name>a</name>
<elements>
<element>
<name>elem</name>
</element>
</elements>
</holder>
I get the error unexpected element (uri:"", local:"element"). Expected elements are <{}link>,<{}totalSize> in the ValidationEventHandler and the tag <elements> (and therefore the elements field in Holder class) is ignored.
When generating the XML both link and totalSize are not outputted as they are nil.
JAVA MODEL
The hierarchy is a bit complex:
(Simplified for the sake of the question)
ElementRoot
abstract ElementRoot has the link member
public abstract class ElementRoot implements Serializable {
protected String link;
#XmlElement(name = "link")
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
Wrapper
abstract Wrapper extends ElementRoot and has the totalSize member
public abstract class Wrapper<T> extends ElementRoot {
protected int totalSize;
protected List<T> collection = new ArrayList<>();
#XmlElement
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public abstract List<T> getCollection();
}
Holder
Holder extends ElementRoot
#XmlRootElement(name = "holder")
#XmlType(propOrder = {"name", "elements"})
public class Holder extends ElementRoot {
private String name;
private Elements elements;
// setters and getters not annotated
}
Elements
Elements extends Wrapper and has a collection of Element
import java.util.Collection;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "elements)
public class Elements extends Wrapper {
#Override
#XmlElement(name="element")
public Collection<Element> getElements() {
return elements;
}
// No setter, to add getElements().add(element)
}
Element
Element extends ElementRoot
#XmlRootElement(name = "element")
#XmlType(propOrder = {"id", "name"})
public class Element extends ElementRoot {
private Integer id;
private String name;
// setters and getters no annotated
}
ENVIRONMENT
I'm using java 7:
JAXB-api 2.2.7
MOXy 2.5.0
There appears to be a bug in EclipseLink JAXB (MOXy) for this use case related to the abstract getCollecion property. We have opened up the following bug that you can use to track our progress on this issue:
http://bugs.eclipse.org/411408
WORK AROUND
Wrapper
We can use #XmlAccessorType(XmlAccessType.NONE) so that only annotated fields/properties will be processed (see: http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html).
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.NONE)
public abstract class Wrapper<T> extends ElementRoot {
protected int totalSize;
protected List<T> collection = new ArrayList<>();
#XmlElement
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public abstract List<T> getCollection();
}
Elements
Since #XmlAccessorType is inherited by the subclasses we will specify XmlAccessType.PUBLIC to return things to normal. Note: I assume the getElements() method in your question should have been getCollection().
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "elements")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class Elements extends Wrapper {
#Override
#XmlElement(name="element")
public List<Element> getCollection() {
return collection;
}
// No setter, to add getElements().add(element)
}

JAXB 2.x: Abstract methods get marshalled as Attribute

I have an abstract root class, let's say A.
And I have several implementation classes extending A.
A has FIELD annotation as well as some #XmlElement annotated properties.
A also has an abstract method.
When marshalling (B extends A), the value returned by the abstract method gets marshalled as attribute. Not as expected, right?
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class SpecialProfile extends ContentNodeBean {
#XmlElement(name="do-index", namespace="my")
private boolean doIndex = false;
public abstract SpecialProfileType getSpecialProfileType();
... getters and setters for properties ...
}
Does anybody have the same issue and how can this be fixed?
I am using org.eclipse.persistence.moxy 2.1.2
I am attempting to reproduce your issue, but so far have been unsuccessful. Can you see where I'm doing something different than you are? The following is my sample code:
A
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class A {
public abstract C getC();
public abstract void setC(C c);
}
B
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class B extends A {
private C c;
#Override
public C getC() {
return c;
}
#Override
public void setC(C c) {
this.c = c;
}
}
C
public class C {
}
Demo
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.eclipse.persistence.Version;
public class Demo {
public static void main(String[] args) throws Exception {
System.out.println(Version.getVersionString());
JAXBContext jc = JAXBContext.newInstance(B.class);
System.out.println(jc);
B b = new B();
b.setC(new C());
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(b,System.out);
}
}
Output
2.1.2.v20101206-r8635
org.eclipse.persistence.jaxb.JAXBContext#100ab23
<?xml version="1.0" encoding="UTF-8"?>
<b xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b"><c/></b>
UPDATE
Based on your comments:
B does not inherit A's XmlAccessorType settings.
It is not the abstract method that you need to mark #XmlTransient, but the field used to implement the accessor on the B class.
The following is what class B should look like:
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class B extends A {
#XmlTransient
private C c;
#Override
public C getC() {
return c;
}
#Override
public void setC(C c) {
this.c = c;
}
}

Categories