I'm switching from Castor to JAXB2 to perform marshaling/unmarshaling between XML and Java object. I'm having problem trying to configure a collection of polymorphic objects.
Sample XML
<project name="test project">
<orange name="fruit orange" orangeKey="100" />
<apple name="fruit apple" appleKey="200" />
<orange name="fruit orange again" orangeKey="500" />
</project>
Project class
The oranges list works fine, I'm seeing 2 oranges in the list. But, I'm not sure how to configure fruitList. The fruitList should have 3 fruit: 2 oranges and 1 apple.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Project {
#XmlAttribute
private String name;
#XmlElement(name = "orange")
private List<Orange> oranges = new ArrayList<Orange>();
// Not sure how to configure this... help!
private List<Fruit> fruitList = new ArrayList<Fruit>();
}
Fruit class
Fruit is an abstract class. For some reason, defining this class as an abstract seems to be causing a lot of problems.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public abstract class Fruit {
#XmlAttribute
private String name;
}
Orange class
public class Orange extends Fruit {
#XmlAttribute
private String orangeKey;
}
Apple class
public class Apple extends Fruit {
#XmlAttribute
private String appleKey;
}
How do I configure my fruitList in Project to achieve what I want here?
Thanks much!
You want to leverage #XmlElementRef this is corresponds to the XML schema concept of substitution groups which corresponds to your question.
Step 1 - Using #XmlElementRef
The fruitList property is annotated with #XmlElementRef:
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Project {
#XmlAttribute
private String name;
#XmlElementRef
private List<Fruit> fruitList = new ArrayList<Fruit>();
}
Step 2 - Annotate Apple and Orange with #XmlRootElement
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Apple extends Fruit {
#XmlAttribute
private String appleKey;
}
and
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Orange extends Fruit {
#XmlAttribute
private String orangeKey;
}
Demo Code
The following code can be used to demonstrate the solution:
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(Project.class, Apple.class, Orange.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Project project = (Project) unmarshaller.unmarshal(new File("input.xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(project, System.out);
}
}
For More Information:
http://bdoughan.blogspot.com/2010/11/jaxb-and-inheritance-using-substitution.html
After futzing around... I think I got it working now:-
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Project {
#XmlAttribute
private String name;
// this has to be commented out, or else oranges won't show up in fruitList
// #XmlElement(name = "orange")
// private List<Orange> oranges = new ArrayList<Orange>();
#XmlElements({
#XmlElement(name = "orange", type = Orange.class),
#XmlElement(name = "apple", type = Apple.class)
})
private List<Fruit> fruitList = new ArrayList<Fruit>();
}
Put XmlAnyElement annotation:
#XmlAnyElement(lax = true)
private List<Fruit> fruitList = new ArrayList<Fruit>();
Related
I wonder if someone can help me with a JAXB problem.
If I have an abstract class with 2 concrete implementations: For example (I have left out most of the markup/xml for brevity):
public abstract class Vehicle{}
public class Car extends Vehicle{}
public class Van extends Vehicle{}
Is there a way to have the xml below unmarshall correctly to the appropriate concrete class
<request>
<car>...</car>
</request>
rather than the following:
<request>
<vehicle xsi:type="car"></vehicle>
</request>
The reason I need this is to be backward compatible with our already published API.
Thanks in advance.
I have just answered in russian speaking community on similar question. Probably you looking for something like that:
#XmlElements({
#XmlElement(name = "car", type = Car.class),
#XmlElement(name = "van", type = Van.class)
})
public List<Vehicle> getVehicles() {
return vehicles;
}
Some quick example:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import java.io.StringReader;
import java.util.List;
public class Test {
public static void main(String... args) throws JAXBException {
String xmldata = "<request><car></car><van></van></request>";
StringReader reader = new StringReader(xmldata);
JAXBContext jaxbContext = JAXBContext.newInstance(Request.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Request request = (Request) unmarshaller.unmarshal(reader);
for (Vehicle object : request.getVehicles()) {
System.out.println(object.getClass());
}
}
}
#XmlRootElement(name = "request")
class Request {
private List<Vehicle> vehicles;
#XmlElements({
#XmlElement(name = "car", type = Car.class),
#XmlElement(name = "van", type = Van.class)
})
public List<Vehicle> getVehicles() {
return vehicles;
}
public void setVehicles(List<Vehicle> vehicles) {
this.vehicles = vehicles;
}
}
abstract class Vehicle {
}
class Van extends Vehicle {
}
class Car extends Vehicle {
}
The output will be:
class Car
class Van
UPD:
Update after comment. For single entry it will work anyway just remove List:
#XmlRootElement(name = "request")
class Request {
private Vehicle vehicles;
#XmlElements({
#XmlElement(name = "car", type = Car.class),
#XmlElement(name = "van", type = Van.class)
})
public Vehicle getVehicles() {
return vehicles;
}
public void setVehicles(Vehicle vehicles) {
this.vehicles = vehicles;
}
}
Hope this will help.
You can use annotations and annotate the concrete implementations. In this case #XmlType() above Car or Van. This way you will keep your xml generic.
This is my xml, need to convert it into java. I had used jaxb
<?xml version="1.0"?>
<lm:order Id="PLG24M240U" JD="" aCount="2" SUCount="1" xmlns:lm="http://www.ae.com/Event/Load">
<lm:master>
<lm:ID>3</lm:ID>
<lm:Number>313</lm:Number>
<lm:ANumber>323</lm:ANumber>
</lm:master>
<lm:detail>
<lm:ID>3</lm:ID>
<lm:Number>3131</lm:Number>
<lm:ANumber>3232</lm:ANumber>
</lm:detail>
<lm:detail>
<lm:ID>3</lm:ID>
<lm:Number>3131</lm:Number>
<lm:ANumber>3232</lm:ANumber>
</lm:detail>
<lm:detail>
<lm:ID>3</lm:ID>
<lm:Number>313</lm:Number>
<lm:ANumber>323</lm:ANumber>
</lm:detail>
</lm:order>
And throwing the following exception
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.ae.com/Event/Load", local:"Order"). Expected elements are <{}lm:Order>
This is my unmarshalling code
jaxbContext = JAXBContext.newInstance(Order.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Order order = (Order) jaxbUnmarshaller.unmarshal(file);
System.out.println(order );
Order Pojo class
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "lm:Order")
public class OrderPay {
#XmlAttribute
private String Id;
#XmlAttribute
private String JD;
#XmlAttribute
private String aCount;
#XmlAttribute
private String pCount;
/*#XmlElement
private Master master;
#XmlElement
private List<Detail> details = new ArrayList<Detail>();*/
}
Can you please help me in reading also, currently reading through file, need to read as an XML String.
The namespace attribute xmlns:lm="http://www.ae.com/Event/Load" might be the culprit here. In order to specify the namespace prefix, you can add the #XmlSchema annotation to a package-info.java file like this:
#XmlSchema(
namespace="http://www.ae.com/Event/Load",
elementFormDefault=XmlNsForm.QUALIFIED),
xmlns={#XmlNs(prefix="lm", namespaceURI="http://www.ae.com/Event/Load")})
package your.package;
import javax.xml.bind.annotation.*;
I have a JAXB-annotated POJO like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Clazz implements Serializable {
#XmlElement(required = false)
private int a;
#XmlElement(required = false)
private int b;
}
I want to mark that either field a or field b is required. With my current set-up, none of them are required, but I want one of them to be present and not the other. How could I achieve it?
You can do the following with #XmlElementRefs
Domain Model
Clazz
import java.io.Serializable;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Clazz implements Serializable {
#XmlElementRefs({
#XmlElementRef(name="a", type=JAXBElement.class),
#XmlElementRef(name="b", type=JAXBElement.class)
})
private JAXBElement<Integer> aOrB;
}
ObjectFactory
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name = "a")
public JAXBElement<Integer> createA(Integer integer) {
return new JAXBElement<Integer>(new QName("a"), Integer.class, integer);
}
#XmlElementDecl(name = "b")
public JAXBElement<Integer> createB(Integer integer) {
return new JAXBElement<Integer>(new QName("b"), Integer.class, integer);
}
}
Demo Code
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Clazz.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum22502171/input.xml");
Clazz clazz = (Clazz) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(clazz, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<clazz>
<b>123</b>
</clazz>
This is what I'm doing:
#XmlType(name = "foo")
#XmlAccessorType(XmlAccessType.NONE)
public final class Foo {
#XmlElement(name = "title")
public String title() {
return "hello, world!";
}
}
JAXB complains:
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
JAXB annotation is placed on a method that is not a JAXB property
this problem is related to the following location:
at #javax.xml.bind.annotation.XmlElement(nillable=false, name=title, required=false, defaultValue=, type=class javax.xml.bind.annotation.XmlElement$DEFAULT, namespace=##default)
at com.example.Foo
What to do? I don't want (and can't) rename the method.
There are a couple of different options:
Option #1 - Introduce a Field
If the value is constant as it is in your example, then you could introduce a field into your domain class and have JAXB map to that:
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlType(name = "foo")
#XmlAccessorType(XmlAccessType.NONE)
public final class Foo {
#XmlElement
private final String title = "hello, world!";
public String title() {
return title;
}
}
Option #2 - Introduce a Property
If the value is calculated then you will need to introduce a JavaBean accessor and have JAXB map to that:
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlType(name = "foo")
#XmlAccessorType(XmlAccessType.NONE)
public final class Foo {
public String title() {
return "hello, world!";
}
#XmlElement
public String getTitle() {
return title();
}
}
There might be a better way, but the first solution that comes to mind is:
#XmlElement(name = "title")
private String title;
public String getTitle() {
return title();
}
Why is it you can't name your method according to Java conventions anyway?
I am having issues to unmarshall nested xml below. Can someone please advise if I am missing something.
body tag can contain any Jaxb anotated obj.
Do I have to create a custom adapter for marshalling/unmarshalling such xml?
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<serviceRq xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="serviceRq">
<body>
<createRq>
<id>1234</id>
</createRq>
</body>
</serviceRq>
My Jaxb-annotated classes are:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "serviceRq")
public class ServiceRq{
private Object body;
<!-- getters and setters omitted-->
}
Here, body can be any jaxb annotated object, in this case its CreateRq.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "createRq")
public class CreateRq{
private String id;
<!-- getters and setters omitted-->
}
I am looking for a generic way to support any Jaxb annotated object in body of the input xml.
You could use a #XmlAnyElement(lax=true) and an XmlAdapter to handle this use case:
ServiceRq
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "serviceRq")
public class ServiceRq{
#XmlJavaTypeAdapter(value=BodyAdapter.class)
private Object body;
// getters and setters omitted
}
BodyAdapter
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class BodyAdapter extends XmlAdapter<Body, Object>{
#Override
public Object unmarshal(Body v) throws Exception {
return v.getValue();
}
#Override
public Body marshal(Object v) throws Exception {
Body body = new Body();
body.setValue(v);
return body;
}
}
Body
import javax.xml.bind.annotation.XmlAnyElement;
public class Body {
private Object value;
#XmlAnyElement(lax=true)
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
CreateRq
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "createRq")
public class CreateRq{
private String id;
// getters and setters omitted
}
Demo
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(ServiceRq.class);
System.out.println(jc);
Unmarshaller unmarshaller = jc.createUnmarshaller();
ServiceRq serviceRq = (ServiceRq) unmarshaller.unmarshal(new File("input.xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(serviceRq, System.out);
}
}
For More Information
http://bdoughan.blogspot.com/2010/08/using-xmlanyelement-to-build-generic.html
http://bdoughan.blogspot.com/2010/07/xmladapter-jaxbs-secret-weapon.html
http://bdoughan.blogspot.com/2010/12/jaxb-and-immutable-objects.html
http://bdoughan.blogspot.com/2010/12/represent-string-values-as-element.html
You could use #XmlAnyElement(lax=true) and the #XmlPath extension in EclipseLink JAXB (MOXy) to handle this use case (Note: I'm the MOXy lead). For an approach that would work with any JAXB implementation (Metro, MOXy, JaxMe, etc) see: Jaxb complex xml unmarshall.
ServiceRq
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "serviceRq")
public class ServiceRq{
#XmlPath("body/createRq")
#XmlAnyElement(lax=true)
private Object body;
// getters and setters omitted
}
CreateRq
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "createRq")
public class CreateRq{
private String id;
// getters and setters omitted
}
Demo
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(ServiceRq.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
ServiceRq serviceRq = (ServiceRq) unmarshaller.unmarshal(new File("input.xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(serviceRq, System.out);
}
}
jaxb.properties
To use MOXy as your JAXB provider you must include a file named jaxb.properties in the same package as your domain model with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
For More Information
http://bdoughan.blogspot.com/2010/08/using-xmlanyelement-to-build-generic.html
http://bdoughan.blogspot.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://bdoughan.blogspot.com/2010/09/xpath-based-mapping-geocode-example.html
http://bdoughan.blogspot.com/2011/03/map-to-element-based-on-attribute-value.html