November 4, 2010

JAXB and Inheritance - Using the xsi:type Attribute

In a previous blog post I was challenged by an Xstream user to describe how JAXB handles inheritance.  In this post I'll describe one strategy that can be used, and how easily this can be done with JAXB.  I'll discuss alternate strategies in future posts.

Java Model 

The model will contain an abstract super class for all types of contact information.

package blog.inheritance;

public abstract class ContactInfo {

}

Address and PhoneNumber will be the concrete implementations of ContactInfo.

package blog.inheritance;

public class Address extends ContactInfo {

    private String street;

    public String getStreet() {
        return street;
    }

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

}

package blog.inheritance;

public class PhoneNumber extends ContactInfo {

}

The Customer object can have different types of contact info set on it, so the property will refer to the super class.

package blog.inheritance;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {

    private ContactInfo contactInfo;

    public ContactInfo getContactInfo() {
        return contactInfo;
    }

    public void setContactInfo(ContactInfo contactInfo) {
        this.contactInfo = contactInfo;
    }

}

Demo Code

We will use the following demo code to demonstrate the use of the xsi:type attribute to represent inheritance.

package blog.inheritance;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        Customer customer = new Customer();
        Address address = new Address();
        address.setStreet("1 A Street");
        customer.setContactInfo(address);

        JAXBContext jc = JAXBContext.newInstance(Customer.class, Address.class, PhoneNumber.class);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

 XML

The following is the resulting XML document.  Note the use of the xsi:type attribute on the contactInfo element.


<customer>
    <contactInfo 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:type="address">
        <street>1 A Street</street>
    </contactInfo>
</customer>

How does this All Work?

First we will take a look at the schema that represents JAXB's view on this object model.  There is an inheritance among the schema types that matches the inheritance among the Java classes.  The contactInfo element in the customer type is of type contactInfo.  When we produce an XML document we can use the xsi:type attribute to cast the element to any of the sub-types.

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="customer" type="customer"/>

    <xs:complexType name="customer">
        <xs:sequence>
            <xs:element name="contactInfo" type="contactInfo" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="contactInfo" abstract="true">
        <xs:sequence/>
    </xs:complexType>

    <xs:complexType name="address">
        <xs:complexContent>
            <xs:extension base="contactInfo">
                <xs:sequence>
                    <xs:element name="street" type="xs:string" minOccurs="0"/>
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="phoneNumber">
        <xs:complexContent>
            <xs:extension base="contactInfo">
                <xs:sequence/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

</xs:schema>

Summary

Not all XML binding tools support inheritance.  The ones that do often use different strategies.  Some include the class name of the subclass as the qualifier, this strategy makes it difficult to send the resulting XML document to another tool.  JAXB on the other hand leverages existing XML schema concepts to produce very portable XML documents.  In a future post I'll discuss how represent inheritance using the element name by leveraging the XML schema concept of substitution groups.




Further Reading

If you enjoyed this post you may also be interested in:

17 comments:

  1. Also, check out my post on representing inheritance using substitution groups:

    - JAXB and Inheritance - Using Substitution Groups

    -Blaise

    ReplyDelete
  2. Hi, very helpful post indeed. I have one question tough. Is it possible to use another (custom) attribute for the type information instead of xsi:type?

    Thanks,
    Darko

    ReplyDelete
  3. Hi, Could you explain me what to do if Address and ContactInfo are not in the same package. Can I add a path to the type?

    ReplyDelete
  4. As long as you include all the necessary classes when you bootstrap the JAXBContext it does not matter what package they are in.

    -Blaise

    ReplyDelete
  5. Hi Blaise;
    Thanks for the notes. Very straightforward! However, I have another (hopefully related) issue. When I create a jaxb project in eclipse, i don't know which library to pick. i have tried a user library that includes all jars from javanet download, no success. Tried the eclipse link library, no success. Even though, I can see impl in the eclipselink and i can see the api in the jre 6 libraries. What am i missing?

    PS: When I create a main class Demo.java, i can marshal java objects as described here
    http://wiki.eclipse.org/EclipseLink/Examples/MOXy/GettingStarted
    The only part that does not seem to be working, is the moxy XmlPath annotation. no errors, but it does not seem to be having any effect.
    Thanks in advance.
    Kamal

    ReplyDelete
  6. Hi Kamai,

    Regarding the JAXB project setup I have posted a reply to your Stack Overflow question:
    - Jaxb project in eclipse indigo

    The following article should help you configure EclipseLink MOXy as your JAXB provider:
    - Specifying EclipseLink MOXy as Your JAXB Provider

    -Blaise

    ReplyDelete
  7. Hi Blaise,

    would you advocate one particular inheritance approach? I have implemented my requirements using both this "xsi:type" approach and the substitution approach (to compare, etc.). Does it simply come down to which XML output style is preferable?

    Great posts btw, thanks!

    Damien

    ReplyDelete
    Replies
    1. Hi Damien,

      I see both representations used. My preference is to use substitution groups. I find the resulting document cleaner and if you register the XML schema with your editor the auto-complete options you get are friendlier.
      - JAXB and Inheritance - Using Substitution Groups

      -Blaise

      Delete
  8. Hi Blaise,
    I have a strange issue with marshalling subclasses.
    I have two subtypes of an abstract class. XML is correct for one subtype (xsi:type attribute generated), but not for the other one : no xml tag generated at all.
    In your example it would just work with address but not with phone.
    Is it a know issue with JAXB/JAXP or should I
    still search an error of my own.
    For info, the classes are generated by xjc from an XSD with correct extensions tags.
    Thx

    ReplyDelete
    Replies
    1. Hi,

      I suspect that your JAXBContext is not aware of both subtypes. Since your model is generated from an XML schema I would recommend creating the JAXBContext based on the generated package name: JAXBContext.newInstance("com.example");. If multiple package names were generated then you could create your JAXBContext like: JAXBContext.newInstance("com.example.foo:com.example.bar");.

      -Blaise

      Delete
  9. Hi Blaise ,
    Can you please help me with how can i remove the xsi:type attribute as this is crating lot issues with our xml parser

    ReplyDelete
    Replies
    1. Hi,

      Have you checked out my other posts about mapping inheritance relationships. Most of them offer alternatives to using the xsi:type attribute:
      - http://blog.bdoughan.com/search/label/Inheritance

      -Blaise

      Delete
  10. Hi Blaise,

    Most of my google searches for the last couple days come to this blog, so I'm posting my question here.

    My issue is that serializing a base class directly sets the root element to be the subclass, not the superclass with the xsi:type attribute set. But I do get the superclass with the xsi:type set when I serialize an object that contains the subclassed object.

    For instance, your example above returns a Customer object that contains a ContactInfo object, and the serialization of the Customer object serializes the embedded ContactInfo superclass with the xsi:type attribute. I get this too, but what happens when trying to serialize the ContactInfo object directly, such that it is the root-element of the generated XML document? I'm hoping that it would be:



    Is there anything in XML standard that says a root-element can't have the xsi:type attribute?

    Thanks,
    Kent

    ReplyDelete
    Replies
    1. Hi Kent,

      The behaviour regarding root objects and inheritance will vary among JAXB implementations. I disagree with how the JAXB reference implementation handles it. If you are using EclipseLink JAXB (MOXy) then you will get the following behaviour.

      - If the parent class is annotated with @XmlRootElement but the child class is not then when a child class is marshalled it will use the root element of the parent class and contain the xsi:type attribute corresponding to the subclass.
      - If the subclass is annotated with @XmlRootElement annotation (different from the parent) then that root element will be used when it is marshalled without the xsi:type attribute.

      You can get the behaviour you are looking for with the JAXB RI by leveraging a JAXBElement as follows:

      Address address = new Address();
      address.setStreet("1 A Street");
      JAXBElement<ContactInfo> je = new JAXBElement<ContactInfo>(new QName("contactInfo"), ContactInfo.class, address);

      Marshaller m = jc.createMarshaller();
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      m.marshal(je, System.out);


      -Blaise

      Delete
  11. Hi Blaise,

    Your JAXB RI suggestion worked - thanks!

    It is unfortunate that this isn't the default behavior. FWIW, I had to create an extra method - the original that returns ContactInfo (for EJB clients) and another that returns String (for REST clients), the latter using your suggestion. Oh well.

    Thanks again,
    Kent

    ReplyDelete