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.
Related
I have got the following scenario in which I got four classes autogenerated (in a JAR):
Class A{
B bEl = ...;
}
Class B{
C cEl = ...;
}
Class C{
D dEl = ...;
}
Class E{
E eEl=...;
}
Setting up those objects it is quite painful and error prone. Therefore, I was wondering if there is a better way to automatically construct a builder. I am aware of Lombok but I cannot edit that code and I cannot add the #Builder annotation.
Any recommendation?
If you are not allowed to change existing classes you can extend them:
public class Existing {
String a;
String b;
public Test(String a, String b) {
this.a = a;
this.b = b;
}
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 class ExistingBuilder extends Existing {
#Builder
public ExistingBuilder(String a, String b) {
super(a, b);
// in case super class doesn't have all arguments constructor just call setters
// setA(a);
// setB(b);
}
}
So as you can see it's doable, but super class should have getters/setters or all args constructor.
I've got an entity which contains a collection of a different type of entities. What I want to do is have JAXB marshal only a select subset of the collection, based on some criteria.
#XmlRootElement
#Entity
public class A{
// other fields
#OneToMany(mappedBy = "x", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Collection<B> bees;
#XmlJavaTypeAdapter(BFormatter.class)
public Collection<B> getBees() {
return bees;
}
public void setBees(Collection<B> bees) {
this.bees= bees;
}
}
#XmlRootElement
#Entity
public class B{
// fields
}
public class BFormatter extends XmlAdapter<Collection<B>, Collection<B>>{
#Override
public Collection<B> unmarshal(Collection<B> v) throws Exception {
return v;
}
#Override
public Collection<B> marshal(Collection<B> v) throws Exception {
Collection<B> subset;
// making subset
return subset;
}
}
This results in errors saying "java.util.Collection is an interface, and JAXB can't handle interfaces" and that "java.util.Collection does not have a no-arg default constructor."
What am I doing wrong, and is this even the right way to go about it?
The important thing is that you can't adapt a Collection (an interface) to something JAXB can handle, since it doesn't marshal an ArrayList or some other collection class. It is designed to marshal (bean) classes containing fields that are Lists or similar, which is meant to "disappear", remaining as the mere repetition of its elements. In other words, there's no XML element representing the ArrayList (or whatever) itself.
Therefore, the adapter has to modify the containing element. (See below for alternatives.) The following classes are working; just assemble a Root element and modify the AFormatter according to your design. (The comments refer to the example at
https://jaxb.java.net/tutorial/section_6_2_9-Type-Adapters-XmlJavaTypeAdapter.html#Type%20Adapters:%20XmlJavaTypeAdapter.)
(Most classes should be modified to avoid making fields public, but as it is, it is brief and working.)
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root{ // Training
#XmlElement
private A a; // Brochure
public Root(){}
public A getA(){ return a; }
public void setA( A value ){ a = value; }
}
#XmlJavaTypeAdapter(AFormatter.class)
public class A{ // Brochure
private Collection<B> bees;
public A(){
bees = new ArrayList<>();
}
public Collection<B> getBees() {
if( bees == null ) bees = new ArrayList<>();
return bees;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public class B{ // Course
#XmlElement
private String id;
public B(){}
public String getId(){ return id; }
public void setId( String value ){ id = value; }
}
public class AFormatter extends XmlAdapter<BeeHive, A>{
#Override
public A unmarshal(BeeHive v) throws Exception {
A a = new A();
for( B b: v.beeList ){
a.getBees().add( b );
}
return a;
}
#Override
public BeeHive marshal(A v) throws Exception {
BeeHive beeHive = new BeeHive();
for( B b: v.getBees() ){
if( b.getId().startsWith("a") ) beeHive.beeList.add( b );
}
return beeHive;
}
}
public class BeeHive { // Courses
#XmlElement(name="b")
public List<B> beeList = new ArrayList<B>();
}
Alternatives: It would be quite simple if the regular getter of the B-list would return the ones that should be marshalled. If the application needs to see all, an alternative getter could be added. Or, the class could have a static flag that instructs the getter to return a List to be used for marshalling, or the regular list at other times.
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>
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;
}
}
Why does example A work, while example B throws a "JAXB annotation is placed on a method that is not a JAXB property" exception?
I'm using JAX-WS with Spring MVC.
Example A
package com.casanosa2.permissions;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name = "FooXMLMapper")
public class FooXMLMapper implements IFoo {
#XmlElement
private final boolean propA;
#XmlElement
private final boolean propB;
public FooMapper(IFoo foo) {
propA = foo.getPropA()
propB = foo.getPropB()
}
public FooMapper() {
propA = false;
propB = false;
}
#Override
public boolean getPropA() {
return propA;
}
#Override
public boolean getPropB() {
return propB;
}
}
Example B
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name = "FooXMLMapper")
public class FooXMLMapper {
private final IFoo foo;
public FooMapper() {
foo = new IFoo() {
#Override
public boolean getPropA() {
return false;
}
#Override
public boolean getPropB() {
return false;
}
};
}
public FooXMLMapper(IFoo foo) {
this.foo = foo;
}
#XmlElement
public boolean getPropA() {
return foo.getPropA();
}
#XmlElement
public boolean getPropB() {
return foo.getPropB();
}
}
I believe the accessors are ignored if it's looking directly at the instance variables and in your example B there are no actual instance variables of the right name. You have to tell it explicitly to use #XmlAccessorType(XmlAccessType.NONE) on the class and #XmlElement and #XmlAttribute on the get/set methods. At least, that's what I ended up doing with my JAXB mapping.
I believe for it to be a proper JAXB property, you would need setters for them as well as getters. (you would likely need a default constructor as well).
I haven't tried your code yet, but it's example A that looks wrong, not B. In example A you have specified the property accessors (get/set methods) but you have annotated the class fields instead (instance variables).