import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElementRefs({ @XmlElementRef(name = "billing-address"), @XmlElementRef(name = "shipping-address") }) private JAXBElement<Address> address; }While useful JAXBElement can get in the way if you want to use your domain model with something like JPA (which doesn't know what to do with it). In this post I will demonstrate how you can eliminate the need for JAXBElement through the use of an XmlAdapter.
Address
The element name and namespace information from JAXBElement needs to be stored somewhere. Instead of using a JAXBElement, we will set the element name and namespace in a QName property on the Address object (line 10). Since we will not be mapping the qName field it has been marked @XmlTransient (line 9, see JAXB and Unmapped Properties).
package blog.jaxbelement.remove; import javax.xml.bind.annotation.*; import javax.xml.namespace.QName; @XmlAccessorType(XmlAccessType.FIELD) public class Address { @XmlTransient private QName qName; private String street; private String city; public QName getQName() { return qName; } public void setQName(QName name) { this.qName = name; } }
AddressAdapter
We will use an XmlAdapter to convert an instance of Address to/from an instance of JAXBElement. During this conversion we must move the name and namespace information between Address and JAXBElement.
package blog.jaxbelement.remove; import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.adapters.XmlAdapter; public class AddressAdapter extends XmlAdapter<JAXBElement<Address>, Address>{ @Override public JAXBElement<Address> marshal(Address address) throws Exception { return new JAXBElement<Address>(address.getQName(), Address.class, address); } @Override public Address unmarshal(JAXBElement<Address> jaxbElement) throws Exception { Address address = jaxbElement.getValue(); address.setQName(jaxbElement.getName()); return address; } }
Customer
The @XmlJavaTypeAdapter annotation is used to specify the XmlAdapter. The XmlAdapter is responsible for converting an instance of Address to a JAXBElement to satisfy the needs of the @XmlElementRefs mapping. The name property on an @XmlElementRef annotation must match the name specified in a @XmlRootElement or @XmlElementDecl annotation.
package blog.jaxbelement.remove; import javax.xml.bind.annotation.*; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElementRefs({ @XmlElementRef(name = "billing-address"), @XmlElementRef(name = "shipping-address") }) @XmlJavaTypeAdapter(AddressAdapter.class) private Address address; public Address getAddress() { return address; } }
ObjectFactory
The @XmlElementDecl annotation is used when a class is associated with multiple elements (if a class is associated with only one element then @XmlRootElement can be used). It is placed on a factory method in a class annotated with @XmlRegistry (when generated from an XML schema this class is always called ObjectFactory). The factory method returns the domain object wrapped in an instance of JAXBElement. The JAXBElement has a QName that represents the elements name and namespace URI.
package blog.jaxbelement.remove; import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.*; import javax.xml.namespace.QName; @XmlRegistry public class ObjectFactory { static final String BILLING_ADDRESS = "billing-address"; static final String SHIPPING_ADDRESS = "shipping-address"; @XmlElementDecl(name=BILLING_ADDRESS) public JAXBElement<Address> createBillingAddress(Address address) { return new JAXBElement<Address>(new QName(BILLING_ADDRESS), Address.class, address); } @XmlElementDecl(name=SHIPPING_ADDRESS) public JAXBElement<Address> createShippingAddress(Address address) { return new JAXBElement<Address>(new QName(SHIPPING_ADDRESS), Address.class, address); } }
input.xml
Below is the input to the demo code. Note how the address data is wrapped in the billing-address element. The billing-address element was one of the element names we specified in a @XmlElementRef annotation on the Customer class. In the demo code we will change this to the shipping-address element, the other element name we specified in an @XmlElementRef annotation.
<?xml version="1.0" encoding="UTF-8"?> <customer> <billing-address> <street>123 A Street</street> <city>Any Town</city> </billing-address> </customer>
Demo
In the demo code below we will:
- Unmarshal the input document (line 14)
- Set a new QName on the Address object. The QName must correspond to one of the @XmlElementDecl annotations on the ObjectFactory class (line 17).
- Marshal the Customer object back to XML (line 21).
package blog.jaxbelement.remove; import java.io.File; import javax.xml.bind.*; import javax.xml.namespace.QName; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class,ObjectFactory.class); Unmarshaller u = jc.createUnmarshaller(); File xml = new File("src/blog/jaxbelement/remove/input.xml"); Customer customer = (Customer) u.unmarshal(xml); // Change the Wrapper Element customer.getAddress().setQName(new QName(ObjectFactory.SHIPPING_ADDRESS)); Marshaller m = jc.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); m.marshal(customer, System.out); } }
Output
Below is the output from running the demo code. Note how the address data is now wrapped in the shipping-address element.
<?xml version="1.0" encoding="UTF-8"?> <customer> <shipping-address> <street>123 A Street</street> <city>Any Town</city> </shipping-address> </customer>
Further Reading
If you enjoyed this post then you may also be interested in:
- JAXB and Root Elements
- XmlAdapter - JAXB's Secret Weapon
- JAXB and Unmapped Properties
- Using JAXB's @XmlAccesorType to Configure Field or Property Access
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.