July 31, 2012

JAXB and Root Elements

@XmlRootElement is an annotation that people are used to using with JAXB (JSR-222).  It's purpose is to uniquely associate a root element with a class.  Since JAXB classes map to complex types, it is possible for a class to correspond to multiple root elements. In this case @XmlRootElement can not be used and people start getting a bit confused.  In this post I'll demonstrate how @XmlElementDecl can be used to map this use case.


XML Schema

The XML schema below contains three root elements:  customer, billing-address, and shipping-address.  The customer element has an anonymous complex type, while billing-address and shipping-address are of the same named type (address-type).
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    targetNamespace="http://www.example.org/customer" 
    xmlns="http://www.example.org/customer" 
    elementFormDefault="qualified">

    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="billing-address"/>
                <xs:element ref="shipping-address"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="address-type">
        <xs:sequence>
            <xs:element name="street" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>

    <xs:element name="billing-address" type="address-type"/>

    <xs:element name="shipping-address" type="address-type"/>

</xs:schema>

Generated Model

Below is a JAXB model that was generated from the XML schema.  The same concepts apply when adding JAXB annotations to an existing Java model.

Customer 

JAXB domain classes correspond to complex types.  Since the customer element had an anonymous complex type the Customer class has an @XmlRootElement annotation.  This is because only one XML element can be associated with an anonymous type.
package org.example.customer;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"billingAddress","shippingAddress"})
@XmlRootElement(name = "customer")
public class Customer {

    @XmlElement(name = "billing-address", required = true)
    protected AddressType billingAddress;

    @XmlElement(name = "shipping-address", required = true)
    protected AddressType shippingAddress;

    public AddressType getBillingAddress() {
        return billingAddress;
    }

    public void setBillingAddress(AddressType value) {
        this.billingAddress = value;
    }

    public AddressType getShippingAddress() {
        return shippingAddress;
    }

    public void setShippingAddress(AddressType value) {
        this.shippingAddress = value;
    }

}

AddressType 

Again because JAXB model classes correspond to complex types, a class is generated for the address-type complex type.  Since multiple root level elements could exist for this named complex type, it is not annotated with @XmlRootElement.
package org.example.customer;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "address-type", propOrder = {"street"})
public class AddressType {

    @XmlElement(required = true)
    protected String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String value) {
        this.street = value;
    }

}

ObjectFactory 

The @XmlElementDecl annotation is used to represent root elements that correspond to named complex types.  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 org.example.customer;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    private final static QName _BillingAddress_QNAME = new QName("http://www.example.org/customer", "billing-address");
    private final static QName _ShippingAddress_QNAME = new QName("http://www.example.org/customer", "shipping-address");

    public ObjectFactory() {
    }

    public Customer createCustomer() {
        return new Customer();
    }

    public AddressType createAddressType() {
        return new AddressType();
    }

    @XmlElementDecl(namespace = "http://www.example.org/customer", name = "billing-address")
    public JAXBElement<AddressType> createBillingAddress(AddressType value) {
        return new JAXBElement<AddressType>(_BillingAddress_QNAME, AddressType.class, null, value);
    }

    @XmlElementDecl(namespace = "http://www.example.org/customer", name = "shipping-address")
    public JAXBElement<AddressType> createShippingAddress(AddressType value) {
        return new JAXBElement<AddressType>(_ShippingAddress_QNAME, AddressType.class, null, value);
    }

}

package-info 

The package-info class is used to specify the namespace mapping (see JAXB & Namespaces).

@XmlSchema(namespace = "http://www.example.org/customer", elementFormDefault = XmlNsForm.QUALIFIED)
package org.example.customer;

import javax.xml.bind.annotation.*;

Unmarshal Operation

Now we look at the impact of the type of root element when unmarshalling XML.

customer.xml

Below is a sample XML document with customer as the root element.  Remember the customer element had an anonymous complex type.
<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="http://www.example.org/customer">
    <billing-address>
        <street>1 Any Street</street>
    </billing-address>
    <shipping-address>
        <street>2 Another Road</street>
    </shipping-address>
</customer>

shipping.xml

Here is a sample XML document with shipping-address as the root element.  The shipping-address element had a named complex type.
<?xml version="1.0" encoding="UTF-8"?>
<shipping-address xmlns="http://www.example.org/customer">
    <street>2 Another Road</street>
</shipping-address>

Unmarshal Demo 

When unmarshalling XML that corresponds to a class annotated with @XmlRootElement you get an instance of the domain object.  But when unmarshalling XML that corresponds to a class annotated with @XmlElementDecl you get the domain object wrapped in an instance of JAXBElement.   In this example you may need to use the QName from the JAXBElement to determine if you unmarshalled a billing or shipping address.
package org.example.customer;

import java.io.File;
import javax.xml.bind.*;

public class UnmarshalDemo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance("org.example.customer");
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        // Unmarshal Customer
        File customerXML = new File("src/org/example/customer/customer.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(customerXML);

        // Unmarshal Shipping Address
        File shippingXML = new File("src/org/example/customer/shipping.xml");
        JAXBElement<AddressType> je = (JAXBElement<AddressType>) unmarshaller.unmarshal(shippingXML);
        AddressType shipping = je.getValue();
    }

}

Unmarshal Demo - JAXBIntrospector

If you don't want to deal with remembering whether the result of the unmarshal operation will be a domain object or JAXBElement, then you can use the JAXBIntrospector.getValue(Object) method to always get the domain object.
package org.example.customer;

import java.io.File;
import javax.xml.bind.*;

public class JAXBIntrospectorDemo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance("org.example.customer");
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        // Unmarshal Customer
        File customerXML = new File("src/org/example/customer/customer.xml");
        Customer customer = (Customer) JAXBIntrospector.getValue(unmarshaller
                .unmarshal(customerXML));

        // Unmarshal Shipping Address
        File shippingXML = new File("src/org/example/customer/shipping.xml");
        AddressType shipping = (AddressType) JAXBIntrospector
                .getValue(unmarshaller.unmarshal(shippingXML));
    }

}

Marshal Operation 

You can directly marshal an object annotated with @XmlRootElement to XML.  Classes corresponding to @XmlElementDecl annotations must first be wrapped in an instance of JAXBElement.  The factory method you you annotated with @XmlElementDecl is the easiest way to do this.  The factory method is in the ObjectFactory class if you generated your model from an XML schema.
package org.example.customer;

import javax.xml.bind.*;

public class MarshalDemo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance("org.example.customer");
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        // Create Domain Objects
        AddressType billingAddress = new AddressType();
        billingAddress.setStreet("1 Any Street");
        Customer customer = new Customer();
        customer.setBillingAddress(billingAddress);

        // Marshal Customer
        marshaller.marshal(customer, System.out);

        // Marshal Billing Address
        ObjectFactory objectFactory = new ObjectFactory();
        JAXBElement<AddressType> je =  objectFactory.createBillingAddress(billingAddress);
        marshaller.marshal(je, System.out);
    }

}

Output

Below is the output from running the demo code.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer xmlns="http://www.example.org/customer">
    <billing-address>
        <street>1 Any Street</street>
    </billing-address>
</customer>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<billing-address xmlns="http://www.example.org/customer">
    <street>1 Any Street</street>
</billing-address>

18 comments:

  1. Is it possible to marshal an AddressType if you don't know if it is of type billing-address or shipping-address?

    ReplyDelete
    Replies
    1. Since AddressType is not annotated with @XmlRootElement you will need to wrap it in an instance of JAXBElement. The QName does not need to be billing-address or shipping-address, you can have it be whatever you like.

      -Blaise

      Delete
    2. Hello Blaise, I'm the Anonymous poster. I really appreciate this article and your response to my question.

      I'm dealing with some legacy systems with perhaps awkward XML schemas. Using your example, what if your Customer had a List of AddressTypes rather than specific Billing and Shipping address fields. In other words, I have a list of addresses and don't know until run time if they are shipping or billing addresses. Would this be possible and how would you get it to Marshal correctly?:


      protected List <AddressType> address;


      So, here is how the XML should look:

      <customer...>
      ...
        <billing-address>...</billing-address>
        <shipping-address>...</shipping-address>
        <shipping-address>...</shipping-address>
        <billing-address>...</billing-address>
      ...
      </customer>

      Delete
    3. Hi Roger,

      What bit of info indicates whether it is a billing-address or shpping-address? Is it a value on the instance?

      -Blaise

      Delete
    4. The root element of the AdressType itself defines the address type in XML, as in your example.

      For a while, I was trying to figure out if I could embed the type in a transient field and somehow tell the marshaller to use that information, but I didn't see a way to do that.

      I'm considering the following solution: Allocate a variable for each type of address (as you did, but make them lists). This should work (unfortunately I have a few more "address types" in the system I'm working on).

      @XmlElement(name = "shipping-address")
      List<ShippingAddress> shippingAddress;

      @XmlElement(name = "billing-address")
      List<BillingAddress> billingAddress;

      Delete
    5. Hi Roger,

      I wrote up the following based on our email conversation:
      - Removing JAXBElement From Your Domain Model

      -Blaise

      Delete
    6. This comment has been removed by a blog administrator.

      Delete
  2. Hi Blaise,

    I'm writing a REST web service. I have a resource B declared in my WADL. I use WADL2Java plugin to generate the code. So, I get an interface declaring the methods on the resource B. So, I've written an implementation class of this interface. I have, for example, a method called getB(String id) that returns a XML representation of an instance of B. But, in my XML schema, B is only defined by a complexType and there is no element of type B. In the generated code, my class B is not annotated with XmlRootElement as expected. But, in the ObjectFactory class, I only have a method that create an instance of B (new B()), no method that creates a JAXBElement that wraps an instance of B. So, I can't do what you propose.

    Do you know what I'm doing wrong?

    Thanks,
    Mickael

    ReplyDelete
    Replies
    1. Hi Mikael,

      It's tough to get enough technical details in the blog comments section. Would you be willing to post your question on:
      - EclipseLink Forum
      - Stack Overflow

      -Blaise

      Delete
  3. I've been reading your posts here and on Stack Overflow about JAXB topics, but still can't get the results I desire. I'm attempting to generate Java classes from a set of XSD files. The XSDs import other XSDs, eg: lifecycle.xsd imports core.xsd, request.xsd imports lifecycle.xsd & core.xsd. When I generate the classes for core.xsd (and create the episode file) the classes seem fine. However, when I then try to use the core episode file during generation of the lifecycle classes, nothing gets generated.
    $ xjc core.xsd -extension -d target -p com.foo.core -episode core.episode

    then:
    $ xjc lifecycle.xsd -extension -d target -p com.foo.lifecycle -b core.episode

    No code gets generated for com.foo.lifecycle. Thoughts?

    ReplyDelete
    Replies
    1. Hi,

      Here is an example I wrote about episode files that may help:
      - Reusing Generated JAXB Classes

      -Blaise

      Delete
  4. Do you as a rule write only for this website or you do that for other online or offline resources?

    ReplyDelete
    Replies
    1. I write mainly for this blog, but several of my articles have been republished on other sites such as Javalobby and Java Code Geeks.

      Delete
  5. If you can have only 2 root elements (customer and billing-address)
    , you could mark AddressType like

    @XmlType(name = "address-type"
    @XmlRootElement(name = "billing-address")

    So, all works fine for me. But,
    is this the right way to do it or it's better with ObjectFactory ?

    Thanks


    ReplyDelete
    Replies
    1. Hi Chema,

      When a class only has one root element I prefer to use the @XmlRootElement annotation. So you could annotate both your Customer and Address classes with @XmlRootElement.

      -Blaise

      Delete
  6. Thanks for this blog post, it was quite helpful for my process that was accepting XML messages that had different root elements.

    ReplyDelete
  7. It looks like what I am looking for, but ... I have about 30 different classes besides the possible error message. So, if everything goes well, the restful service will give me a normal XML for the class that corresponds to the call I made, but if not, then I get the dreaded error xml document. Does my ObjectFactory have to handle all the possible object types? Can I be more dynamic about it?

    ReplyDelete
    Replies
    1. Associating a root element with with a class is useful when you don't know which class in your object model the XML corresponds to. In the case of a RESTful service you may know that the XML you receive from a POST operation that corresponds to creating a customer will always correspond to the Customer class. In this case you can ignore @XmlRootElement and @XmlElementDecl all together and just use an unmarshal method that takes a Class parameter (i.e. Customer customer = unmarshaller.unmarshal(xml, Customer.class).getValue()).

      Delete