In a previous blog post I described how to leverage the xsi:type attribute to represent inheritance. In this post I'll demonstrate how to use the element name instead by leveraging the XML Schema concept of substitution groups.
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. Since we will be using the element name as the inheritance indicator we will annotate each of the sub-classes with @XmlRootElement.
package blog.inheritance; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Address extends ContactInfo { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
package blog.inheritance; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement 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. We will annotate the contactInfo property with @XmlElementRef to indicate the value type will be derived from the element name (and namespace URI).
package blog.inheritance; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Customer { private ContactInfo contactInfo; @XmlElementRef 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 that the Address object is marshalled to the address element.
<customer> <address> <street>1 A Street</street> </address> </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. Each type has a corresponding global element. The address and phoneNumber elements specify that they may be substituted for the contactInfo element.
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="customer" type="customer"/> <xs:element name="contactInfo" type="contactInfo"/> <xs:element name="address" type="address" substitutionGroup="contactInfo"/> <xs:element name="phoneNumber" type="phoneNumber" substitutionGroup="contactInfo"/> <xs:complexType name="customer"> <xs:sequence> <xs:element ref="contactInfo"/> </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
Further Reading
If you enjoyed this post you may also be interested in:
Typo! Schema is not valid: the second "address" element declaration should be "phoneNumber"
ReplyDeleteHello Sten,
ReplyDeleteThanks for pointing that out, I have made the correction. I hope you found the article useful.
-Blaise
Hi Blaise. I found this writeup via StackOverflow, and it's helpful except... I want to do this for a parameter on a SOAP method, but @XmlElementRef can't be used in parameters. for example:
ReplyDeletepublic void setContactInfo(int userID, /* @XmlElementRef */ ContactInfo newInfo) { ... }
Any suggestions?
Hi Cody,
ReplyDeleteGood question, I've tried a couple of different things without much look. You may need to have a method per contact type):
- public void setAddress(int userID, Address)
- public void setPhoneNumber(int userID, PhoneNumber)
You may want to ask this question on the forum below:
- Java WS & XML Community News
-Blaise
How do you handle this if Address, PhoneNumber and ContactInfo all need XmlAdapters? XmlJavaTypeAdapter on a class is mutually exclusive with any other annotations.
ReplyDeleteFor example here, I have a representation of a state machine with all the states, transitions, actions, etc... The parent object has a Set of States, each state has properties common to each typeOfState, and each typeOfState has its own unique properties, some of which can't be directly marshaled.
How can JAXB handle this?
Here are a couple of answers I gave to questions on Stack Overflow that may help:
Delete- http://stackoverflow.com/a/3290816/383861
- http://stackoverflow.com/a/8943920/383861
-Blaise
Gretings, thanks for this very interesting article!
ReplyDeleteUsing this inheritance principle, I'd like to use it with a list of abstract classes instead of a simple element, such as:
@XmlRootElement
public class Customer {
private List contactInfoList;
@XmlElementRef
public List<ContactInfo getContactInfoList() {
return contactInfoList;
}
But somehow if I don't add all possible subclasses in a XmlElementRefs I don't retrieve my objects, so I have to set as:
@XmlElementRefs({@XmlElementRef(Adress.class),@XmlElementRef(PhoneNumber.class)})
My goal is to allow ANY subclass of ContactInfo to be set in my list, (and even subclasses which I don't know yet), so how to avoid this classes-list-identified-in-annotation-only limitation?
Thnaks a lot!
I apologize for the delay in getting back to you. The JAXBContext will need to be aware of all the subclasses that you plan to use.
DeleteYou could achieve this by using an @XmlSeeAlso({Address.class, PhoneNumber.class}) annotation on the ContactInfo class. Or as in this example you just need to include all the possible subclasses when you bootstrap the JAXBContext.
-Blaise
That's the solution in my example also!
ReplyDeleteThanks for this most elucidating post.
Now unmarshal it...
ReplyDeletejavax.xml.bind.UnmarshalException: Unable to create an instance of blog.inheritance.ContactInfo
Hi Jamie,
DeleteI responded to your question on Stack Overflow with what is probably the problem that you are seeing:
- http://stackoverflow.com/questions/11219074/jaxb-xsitype-subclass-unmarshalling-not-working/11224165#11224165
-Blaise
Wow! Great post! :D
ReplyDeleteokay, suppose I have one such collection
@XmlElementRefs({
@XmlElementRef(name="ElementA", type=ClassA),
@XmlElementRef(name="ElementB", type=ClassB) }
)
List items;
Now how do I access every individual element of this list? Is the following code correct?
for (int j = 0; j < items.size(); ++j) {
if (items.get(i).getClass().equals(ClassA)) {
// perform ClassA specific processing:
} else if (items.get(i).getClass().equals(ClassB)) {
// perform ClassB specific processing:
}
}
Is this correct approach? Is there a better way to perform each class specific processing? I mean is there a way to avoid those if else constructs?
You could use that code. If ClassA and ClassB share a common super type or implement a common interface then you could introduce a common method that both classes implement to do the class specific processing.
DeleteHi Blaise,
ReplyDeleteI am trying to implement this but on the root Element.
I have posted my query on the StackOverFlow:
http://stackoverflow.com/q/17241634/2367540
Looking forward to hear from you.
I have posted an answer on to your Stack Overflow question:
Delete- http://stackoverflow.com/a/17242157/383861
Hi Blaise,
ReplyDeleteThank you for the tutorial. However I can't find out how to do when the XML generated needs to have all the same element names? For example in your tutorial:
1 A Street
35689457
instead of having
1 A Street
35689457
In this cas, unmarshalling doesn't work.
The XML element names did not survive the constraints of the comment area. However if you don't want to differentiate the subtypes by the element name then you need to use something else as the inheritance indicator. Below are some different options:
Delete- JAXB and Inheritance - Using the xsi:type Attribute
- JAXB and Inheritance - MOXy Extension @XmlDescriminatorNode/@XmlDescrimintatorValue
- JAXB and Inheritance - Using XmlAdapter
Thank you very much for your answer which is exactly what I was looking for. A few clarifications for those who, like me, want to use the same element name for all the subtypes :
Delete- JAXB and Inheritance - Using the xsi:type Attribute, is the basic solution you have probably already found if ou were facing the same problem. The only inconvenience is that this solution adds a namespace tag for each of you subtype which makes your xml not as clear as before
- JAXB and Inheritance - MOXy Extension @XmlDescriminatorNode/@XmlDescrimintatorValue : Probably a very good solution but you need to use JAXB implemented in the framework EclipseLink instead of the JAXB included in the jdk.
- JAXB and Inheritance - Using XmlAdapter : Perfect solution for this problem, even if it involves writing a few lines of code, which is maybe not what you want to do when using the JAXB annotations
Thank you for clear Explanation
ReplyDelete