Can JAXB initialize values in base classes? - java

I am working on a Scala project, and we want to use XML to initialize our objects with JAXB (not Spring). I have a hierarchy where more data members get added in the subclasses. A simple example would look something like this:
class Animal
{
string name
}
class Cat extends Animal
{
int numLives
}
class Dog extends Animal
{
bool hasSpots
}
I would like to be able to initialize a list of animals from an XML block that looks something like this:
<Animals>
<Cat>
<name>Garfield</name>
<numLives>9</numLives>
</Cat>
<Dog>
<name>Odie</name>
<hasSpots>false</hasSpots>
</Dog>
</Animals>
How would we setup the annotations in the classes to be able to handle this?

For this example you will want to make use of the #XmlElementRef and #XmlRootElement annotations. This corresponds to the XML schema concept of substitution groups. This will allow you to have a list of objects from an inheritance hierarchy differentiated by element.
Animals
This will serve as the root object for the domain model. It has a List property annotated with #XmlElementRef. This means it will match values based on the value of their #XmlRootElement annotations.
package forum8356849;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Animals")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({Cat.class, Dog.class})
public class Animals {
#XmlElementRef
private List<Animal> animals;
}
Animal
package forum8356849;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
class Animal
{
String name;
}
Cat
We will annotate the Cat class with an #XmlRootElement annotation. This is used in tandem with the #XmlElementRef annotation on Animals.
package forum8356849;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Cat")
class Cat extends Animal
{
int numLives;
}
Dog
We will also add an #XmlRootElement annotation to the Dog class.
package forum8356849;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Dog")
class Dog extends Animal
{
boolean hasSpots;
}
Demo
You can use the following class to see that everything works as expected. input.xml corresponds to the XML provided in your question.
package forum8356849;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Animals.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum8356849/input.xml");
Animals animals = (Animals) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(animals, System.out);
}
}
For More Inforation
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html

In such cases, I prefer creating an XSD schema and generating code from it, so you are on safe side.
But to answer your question, yes, you can. The annotations are XMLElement, XMLAttribute, XMLRootElement.

Related

JAXB adding annotated classes with new JAR

Have following situation:
A class with a List field like:
#XMLType
#XMLAccessType(XMLAccessorType.FIELD)
#XMLRootElement(name = "container")
public class ListContainer {
#XMLElementWrapper(name="elements")
private List<Element> elements = new ArrayList<>();
....
}
The Element class is an abstract JAXB annotated class with #XMLRootElement annotation like:
#XMLType
#XMLRootElemenent
public abstract class Element {
....
}
These classes define some kind of Framework and users shall be able to add own implementations of Element class within own JAR packages. What I would like to achieve, is that after unmarshalling I would have in elements field of the instance of class ListContainer class instances which were introduced as extensions to the framework. For instance let say there is a class DummyElement in some other ext1.jar, which is in the class path, and it looks like following:
#XMLType
#XMLRootElement(name = "dummy")
public class DummyElement extends Element {
....
}
in ext2.jar I will have EasyElement like:
#XMLType
#XMLRootElement(name = "easy")
public class EasyElement extends Element {
...
}
in xml there on the place I would have something like:
<container>
<elements>
<dummy>....</dummy>
<easy>...</easy>
<easy>....</easy>
<dummy>...</dummy>
</elements>
</container>
expected result should be, that unmarshalled instance of ListContainer class will have 2 DummyElement instances and 2 EasyElement instances in elements fields.
So far if I leave ListContainer class annotated like this - I will have nothing in list. If I annotated with #XMLAnyElement(lax=true) then I will have ElementNSImpl instances.
Thank you for ideas in advance.
Update:
The solution is in control over creation of JAXBContext. I created service interface, which delivers me a list of classes required for context like:
public interface XMLContextProvider {
Set<Class> getJAXBContextClasses();
}
Then I created in framework a class which implements this interface and lists all classes I require from side of the framework. The same was done for extensions. The classes registered in META-INF/services - see ServiceLoader. Then I created a utility class, which utilises the ServiceLoader to find all providers and creates the JAXBContext with the list of all classes gathered from all providers. With this context it is possible to Marshal and Unmarshal. Additionally as I utilise JAX-RS I have created a Resolver for JAXBContext:
#Provider
public class XMLContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext ctx;
public XMLContextResolver() {
ctx = <here goes call to utility class>
}
#Override
public JAXBContext getContext(Class<?> type) {
if (this.classes.contains(type)) {
return this.ctx;
}
return null;
}
}

XmlDiscriminatorNode/XmlDiscriminatorValue with multiple levels of inheritance

I have the following domain model:
#XmlDiscriminatorNode("#type")
class Vehicle
#XmlDiscriminatorValue("car")
class Car extends Vehicle
#XmlDiscriminatorValue("tank")
class Tank extends Vehicle
#XmlDiscriminatorValue("sedan")
class Sedan extends Car
In my top level model class I have a List<Vehicle> that I want to serialize. I want to serialize this model with moxy so that the fields in Sedan show up. When I add XmlDiscriminatorNode to Vehicle and add XmlDiscriminatorValue to the extension classes, only the first level of inheritance is serialized. In the above example, the attributes of Car show up but not the attributes of Sedan. How do I annotate these classes so that Sedan's attributes show up?
When JAXB derives metadata for a model transitive relationships are processed. When a class is processed its super classes are also processed, but not its subclasses. To overcome this you will need to make JAXB aware of the subclasses. One way to do this is to use the #XmlSeeAlso annotation.
Java Model
I will use the Java model from your question with the following changes:
Vehicle
I have added the #XmlSeeAlso annotation. Only the leaf classes need to be added, as processing Sedan will cause the Car class to be processed.
import javax.xml.bind.annotation.XmlSeeAlso;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
#XmlDiscriminatorNode("#type")
#XmlSeeAlso({Tank.class, Sedan.class})
class Vehicle {}
Sedan
I have added a property to the Sedan class to show that it appears in the marshalled result.
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
#XmlDiscriminatorValue("sedan")
class Sedan extends Car {
private String sedanProperty;
public String getSedanProperty() {
return sedanProperty;
}
public void setSedanProperty(String sedanProperty) {
this.sedanProperty = sedanProperty;
}
}
Foo
Here is a root class as you describe in your question that has a List of Vehicle objects.
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Foo {
private List<Vehicle> vehicles = new ArrayList<Vehicle>();
#XmlElement(name="vehicle")
public List<Vehicle> getVehicles() {
return vehicles;
}
}
Demo Code
Demo
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Sedan sedan = new Sedan();
sedan.setSedanProperty("Sedan Value");
Foo foo = new Foo();
foo.getVehicles().add(sedan);
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>
<vehicle type="sedan">
<sedanProperty>Sedan Value</sedanProperty>
</vehicle>
</foo>

Jaxb: Unmarshalling xml with multiple namespaces in same package

I'm new to using namespaces in xml so I am kind of confused and would like some clarification. I have a java service where I am receiving xml documents with many different namespaces and while i got it working, I feel like I must have done something wrong so I want to check. In my package-info.java I have my schema annotation such as:
#javax.xml.bind.annotation.XmlSchema(
xmlns={
#javax.xml.bind.annotation.XmHS(prefix="train", namespaceURI="http://mycompany/train"),
#javax.xml.bind.annotation.XmHS(prefix="passenger", namespaceURI="http://mycompany/passenger")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm=QUALIFIED
)
I have a Train.java annotated on the class level with:
#XmlRootElement(name="Train", namespace="http://mycompany/train")
and each field in the class annotated with:
#XmlElement(name="Color") for example
Train contains a List of Passenger(s) so there's a property
private Set<Passenger> passengers;
and this collection is annotated with:
#XmlElementWrapper(name="Passengers")
#XmlElements(#XmlElement(name="Passenger", namespace="http://mycompany/passenger"))
Then within Passenger.java the class itself is annotated with:
#XmlElement(name="Passenger", namespace="http://mycompany/passenger")
Finally for individual fields within Passenger.java, they are annotated like this:
#XmlElement(name="TicketNumber", namespace="http://mycompany/passenger")
So when I have an xml that looks like:
<train:Train>
<train:Color>Red</train:Color>
<train:Passengers>
<train:Passenger>
<passenger:TicketNumber>T101</passenger:TicketNumber>
</train:Passenger>
</train:Passengers>
</train:Train>
Now I unmarshal this xml I received and Train's Color property is set and Passenger's TicketNumber property is set. But I don't know why I need to add the namespace url on the XmlElement annotation on TicketNumber for that to work but I didn't need to do so for the Color property on Train. If I remove the namespace attribute from the XmlElement annotation on TicketNumber, the value from the xml wont get mapped to the object unless I also remove the namespace prefix from the xml request. I feel like since I've got the namespace attribute defined on the XmlRootElement for Passenger, I shouldn't need to do that for every single field in the class as well just like I didn't have to for Train so I am assuming I must have setup something wrong. Can someone point me in the right direction? Thanks!
Below is an explanation of how namespaces work in JAXB (JSR-222) based on your model.
JAVA MODEL
package-info
Below is a modified version of your #XmlSchema annotation. It contains some key information:
namespace - The default namespace that will be used to qualify global elements (those corresponding to #XmlRootElement and #XmlElementDecl annotations (and local elements based on the elementFormDefault value) that don't have another namespace specified.
elementFormDefault by default only global elements are namespace qualified but by setting the value to be XmlNsForm.QUALIFIED all elements without an explicit namespace specified will be qualified with the namespace value.
xmlns is the preferred set of prefixes that a JAXB impl should use for those namespaces (although they may use other prefixes).
#XmlSchema(
namespace="http://mycompany/train",
elementFormDefault = XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(prefix="train", namespaceURI="http://mycompany/train"),
#XmlNs(prefix="passenger", namespaceURI="http://mycompany/passenger")
}
)
package forum15772478;
import javax.xml.bind.annotation.*;
Train
Since all the elements corresponding to the Train class correspond to the namespace specified on the #XmlSchema annotation, we don't need to specify any namespace info.
Global Elements - The #XmlRootElement annotation corresponds to a global element.
Local Elements - The #XmlElementWrapper and #XmlElement annotations correspond to local elements.
package forum15772478;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Train")
public class Train {
private List<Passenger> passengers;
#XmlElementWrapper(name="Passengers")
#XmlElement(name="Passenger")
public List<Passenger> getPassengers() {
return passengers;
}
public void setPassengers(List<Passenger> passengers) {
this.passengers = passengers;
}
}
Passenger
If all the elements corresponding to properties on the Passenger class will be in the http://mycompany/passenger namespace, then you can use the #XmlType annotation to override the namespace from the #XmlSchema annotation.
package forum15772478;
import javax.xml.bind.annotation.*;
#XmlType(namespace="http://mycompany/passenger")
public class Passenger {
private String ticketNumber;
#XmlElement(name="TicketNumber")
public String getTicketNumber() {
return ticketNumber;
}
public void setTicketNumber(String ticketNumber) {
this.ticketNumber = ticketNumber;
}
}
Alternatively you can override the namespace at the property level.
package forum15772478;
import javax.xml.bind.annotation.*;
public class Passenger {
private String ticketNumber;
#XmlElement(
namespace="http://mycompany/passenger",
name="TicketNumber")
public String getTicketNumber() {
return ticketNumber;
}
public void setTicketNumber(String ticketNumber) {
this.ticketNumber = ticketNumber;
}
}
DEMO CODE
The following demo code can be run to prove that everything works:
Demo
package forum15772478;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Train.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum15772478/input.xml");
Train train = (Train) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(train, System.out);
}
}
input.xml/Output
In the XML below I have added the necessary namespace declarations that were missing from the XML document in your question.
<train:Train
xmlns:train="http://mycompany/train"
xmlns:passenger="http://mycompany/passenger">
<train:Color>Red</train:Color>
<train:Passengers>
<train:Passenger>
<passenger:TicketNumber>T101</passenger:TicketNumber>
</train:Passenger>
</train:Passengers>
</train:Train>
FOR MORE INFORMATION
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html

Is it possible to programmatically configure JAXB?

Say I have two JavaBeans Person and Address.
If I create a list of Person objects, I'd like to marshal to something like this:
<persons>
<person>...</person>
</persons>
It's possible to use the technique described here:
Using JAXB to unmarshal/marshal a List<String>
By annotating JaxbList with #XmlRootElement(name = "persons") and #XmlElement(name = "person"), then it's possible to marshal to the XML above.
But, it'd be nice to be able to reuse the same JaxbList<T> class to also marshal a list of Address objects. And in reality, I will have many other types of beans. I can go with something like:
<list>
<item xsi:type="person" xmlns:xsi="http://www.w2.org/2001/XmlSchema-instance"></item>
</list>
But, ideally, it'd be nice to have it replace "list" with the plural version of class name and "item" with the class name.
So, is it possible to programmatically configure the JaxbContext or something during runtime and essentially set the value of the name inside #XmlRootElement and #XmlElement?
Or any other way to get this working without having to write a separate implementation of JaxbList for every bean type? Maybe XmlJavaTypeAdapter can achieve this sort of thing?
Update
#Blaise Doughan's solution accepted below works great. For my use case, I needed to go straight from Java object to XML, here's what worked (note this is not my full implementation, it's sort of just pseudo code for demonstration):
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}
You could create a generic Wrapper object like the following:
Wrapper
You could create a generic wrapper class with a List property annotated with #XmlAnyElement(lax=true). The type of the object used to populate this list will be based on its root element (see: http://blog.bdoughan.com/2010/08/using-xmlanyelement-to-build-generic.html).
package forum13272288;
import java.util.*;
import javax.xml.bind.annotation.XmlAnyElement;
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
Address
You will need to annotate the possible contents of the list with #XmlRootElement.
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Address {
}
Person
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Person {
}
Demo
The demo code below demonstrates how to use the Wrapper class. Since the root element can be different you will need to specify that you want to unmarshal to the wrapper class. Alternatively you could leverage the #XmlElementDecl annotation to associate multiple root elements with the wrapper class (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).
package forum13272288;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, Person.class, Address.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StreamSource personsXML = new StreamSource("src/forum13272288/persons.xml");
JAXBElement<Wrapper> wrapper1 = unmarshaller.unmarshal(personsXML, Wrapper.class);
marshaller.marshal(wrapper1, System.out);
StreamSource addressesXML = new StreamSource("src/forum13272288/addresses.xml");
JAXBElement<Wrapper> wrapper2 = unmarshaller.unmarshal(addressesXML, Wrapper.class);
marshaller.marshal(wrapper2, System.out);
}
}
Output
Below is the output from running the demo code. The files persons.xml and addresses.xml look just like there corresponding output.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons>
<person/>
<person/>
</persons>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addresses>
<address/>
<address/>
</addresses>
For More Information
http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html

What package-info do I annotate with XmlJavaTypeAdapters?

I've studied Blaise Doughan's answer to a question on this subject but have a further question.
XmlJavaTypeAdapters lets you list a bunch of XmlJavaTypeAdapter annotations, each of which governs how a non-bindable type is mapped to a bindable type by JAXB.
You can use this annotation at the package level. When you do so, every XmlJavaTypeAdapter annotation needs its type() attribute fully specified.
There does not appear to be a requirement that the package that is being annotated have anything to do with the package of the non-bindable types being adapted. That is convenient and nice.
That, however, leads me to my next question: if there is no relationship between the annotated package and the package of the type being adapted, how does JAXB discover package-level XmlJavaTypeAdapters annotations? How, in other words, does it know which packages to consult for potential XmlJavaTypeAdapters annotations? May I make a random package in, say, a .jar file in my .ear file's lib directory that contains a single, ginormous package-info class that is annotated with all the adapters for all of my non-bindable types?
When the JAXB runtime loads a JAXB-annotated class, it looks for a package-info.java in the same package as that class, and checks that to look for package-level annotations. So while XmlJavaTypeAdapters doesn't have to reside in the same package as the "non-bindable" types, it does have to reside in the same package as the "bindable" types.
For example, say I have a JAXB-annotated class A, in package X, which has a property of type B in package Y. In order to bind the B property, let's say a type adapter is required. That adapter can be specified in A itself, or it can be specified in the package-info.java in package X. Package Y is pretty much arbitrary, and is of no interest to the JAXB runtime.
I hope that makes sense.
There does not appear to be a requirement that the package that is
being annotated have anything to do with the package of the
non-bindable types being adapted. That is convenient and nice.
This is correct. When #XmlJavaTypeAdapter is used at the package level it means apply this adapter to all properties of the specified type for classes that reside in this package. I'll demonstrate below with an example.
forum8735737.bar.package-info
For this package we will specify an XmlAdapter that will be applied to all fields/properties of type String within this package.
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum8735737.bar;
import javax.xml.bind.annotation.adapters.*;
forum8735737.bar.StringAdapter
Our XmlAdapter will simply convert all instances of String to upper case when marshalling:
package forum8735737.bar;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String v) throws Exception {
return v;
}
#Override
public String marshal(String v) throws Exception {
if(null == v) {
return v;
}
return v.toUpperCase();
}
}
forum8735737.bar.Bar
Bar represents a POJO in this package with a property of type String:
package forum8735737.bar;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Bar {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
forum8735737.foo.Foo
Foo represents a domain object with a property of type String that exists in a different package. The XmlAdapter we registered for the forum8735737.bar package will not apply to this class:
package forum8735737.foo;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Demo
The following code will create instances of both Foo and Bar and marshal them to XML:
package forum8735737;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import forum8735737.bar.Bar;
import forum8735737.foo.Foo;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class, Bar.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Foo foo = new Foo();
foo.setName("Foo");
marshaller.marshal(foo, System.out);
Bar bar = new Bar();
bar.setName("Bar");
marshaller.marshal(bar, System.out);
}
}
Output
Notice how the value of the name element within bar has been converted to upper case:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<name>Foo</name>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<bar>
<name>BAR</name>
</bar>

Categories