In previous blog posts I described how to leverage the xsi:type attribute and substitution groups to represent inheritance. In this post I'll demonstrate how an upcoming EclipseLink 2.2 JAXB (MOXy) extension can be used to represent inheritance. It leverages an attribute of your own choosing to represent the subtype. If you are using a version of EclipseLink prior to 2.2 you can use the technique described here.
Java Model
The model will contain an abstract super class for all types of contact information. We will use MOXy's @XmlDescriminatorNode annotation to specify the XML attribute we wish to use to indicate the appropriate subtype.
package blog.inheritance; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; @XmlDiscriminatorNode("@classifier") public abstract class ContactInfo { }
Address and PhoneNumber will be the concrete implementations of ContactInfo. By default the type name will be used with the discriminator node, we can override this by using @XmlDescriminatorValue.
package blog.inheritance; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("address-classifier") 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 org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("phone-number-classifier") 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; } }
Java Model - jaxb.properties
In order to specify that we are using the MOXy JAXB implementation we need to put a file called jaxb.properties in with our Address class with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo Code
We will use the following demo code to demonstrate the use of the descriminator node 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 contactInfo element with the classifier attribute containing the discriminator node value "address-classifier".
<customer> <contactInfo classifier="address-classifier"> <street>1 A Street</street> </contactInfo> </customer>
How does this All Work?
This is very similar to using the xsi:type attribute to represent the sub type. However in this case we are not leveraging an XML schema concept. This is not necessarily a bad thing, as there are legitimate use cases where this type of behaviour is required (check out this Stack Overflow post for an example). It is also useful when you need to interact with XML binding tools with proprietary mechanisms for handling inheritance.
Further Reading
If you enjoyed this post you may also be interested in:
Can PhoneNumber/Address be also @XmlRootElement?
ReplyDeleteSo will this example work:
Address address = new Address();
address.setStreet("1 A Street");
JAXBContext jc = JAXBContext.newInstance(Address.class, PhoneNumber.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(address, System.out);
?
Andrew,
ReplyDeleteYes, this concept can also be used when the classes in the inheritance hierarchy (Address & PhoneNumber in this example) are the root objects.
-Blaise
Which jar files I need to have in classpath to use MOXy, if I use latest Oracle JDK 1.6u22?
ReplyDeleteIs eclipselink.jar enough?
Classes from eclipselink\jlib\moxy (as I can see) already exist in jdk, but your are recent.
Do I need them or old-bundled-in-JDK are enough?
Andrew,
ReplyDeleteWhen using JDK 1.6 only eclipselink.jar is required. The supporting jars are provided for those using JDK 1.5 (which does not include items like the JAXB public API).
-Blaise
But classes in your supporting jars are newer than bundled, may be they are more stable, faster, etc? ;-)
ReplyDeleteOr one can use only eclipselink.jar without any doubts?
Andrew,
ReplyDeleteWith JDK 1.6 I would still use the versions of the classes it contains. You can override the libraries in the JDK using the endorsed mechanism, but it is not necessary in order to use MOXy
-Blaise
Thank You Blaise!
ReplyDeleteYour blog and your support are outstanding!
Excellent blog,
ReplyDeleteI'm trying to use XmlID and XmlIDRef with Inheritence. Eg my class with id attribute annotated with XmlID inherits from an abstract base class. However I keep getting ArrayIndexOutOfBoundsException -1. The same error happens with the xsi:type and xmldescriminator methods. Any ideas?
Hi Denis,
ReplyDeleteCan you provide more details on your use case through the "Contact Me" page?:
-Blaise
Is it possible to "discriminate" based on the value of a child element rather than on an attribute?
ReplyDeleteHi Denis,
ReplyDeleteThank you for sending the details of your use case. I haven't been able to reproduce the issue, but have emailed you what I have tried so far.
-Blaise
Hi Kevin,
ReplyDeleteCurrently the descriminator node must be an XML attribute. I have opened an enhancement request to expand this feature to cover child elements:
- Bug 341149 - Enhancement: Expand @XmlDescriminatorNode to support child elements
-Blaise
What I do not understand is the following:
ReplyDeleteCoding a line like this:
JAXBContext jc = JAXBContext.newInstance(Customer.class, Address.class, PhoneNumber.class);
fully breaks the OO design. I do not want to hardcode all existing subclasses of ContactInfo.class, I would expect that JAXB finds the correct subclass by itself. Is this not possible?
Cheers
Balz
Hi Balz,
ReplyDeleteUsing Java reflection you can not ask a class for all of its subclasses. Some people leverage the @XmlSeeAlso annotation on the parent class, and specify the subclasses there to avoid including them in the creation of the JAXBContext.
-Blaise
Hello,
ReplyDeleteI have a problem when I write the model to output.
I have downladed the EclipseLink Target Component and I started to copy your example. But when the JAXB write to the output, he does not write descriminator attribute but xmlns:xsi=... instead.
What is wrong ?
Thank You for your help
Laurent
Hello Bibi,
ReplyDeleteHave you included the jaxb.properties file? The jaxb.properties file is what specifies that EclipseLink JAXB (MOXy) should be used as the JAXB provider:
- Specifying EclipseLink MOXy as Your JAXB Provider
-Blaise
Hi Bibi/Laurent,
ReplyDeleteHave you correctly specified EclipseLink JAXB (MOXy) as the JAXB provider? This is done via a jaxb.properties file:
- Specifying EclipseLink MOXy as Your JAXB Provider
-Blaise
Hello Blaise,
ReplyDeleteWhen I try to add the "org.eclipse.persistence.moxy" plugin, the application can not start. Any idea ?
Thank You
Best regards
bibi
Hi Bibi,
ReplyDeleteWhat exception are you getting when you try to start your application? You will need to ensure that you have all the supporting bundles:
- Required bundles for MOXy
If you are using MOXy in a GlassFish environment the following may help:
- Creating a RESTful Web Service - Part 3/5
-Blaise
Hi Blaise,
ReplyDeleteI think I have the same or similar question than Denis O'Sullivan. Would you please post what you found out so far?
Thanks
Torsten
I switched to MOXy to leverage the XmlCDATA annotation, but now I see that inheritance works differently from the stock marshaller.
ReplyDeleteIn my current implementation I have @XmlTransient, as well as several @XmlElement fields, on the super class. This way I can use fields from the super class in the propOrder of @XmlType in the children classes.
How do I use fields from the super class in MOXy, and how do I order them in the children classes' @XmlType(propOrder)?
Hi Torsten,
ReplyDeleteDenis was able to get around the issue he was seeing by marking the base class @XmlTransient. Could you let me know if this would work for your use case?
-Blaise
Hi Henrik,
ReplyDeleteYou should get the same behaviour in MOXy. Which version are you using?
For more information:
- Ignoring Inheritance with @XmlTransient
-Blaise
Hi Blaise,
ReplyDeleteThanks for your fine article.
My problem for the week is:
How to serialize a java base class and set the Xml-element name to a value that I'll know only at runtime?
Any chance to do that?
kind regards Franz-Josef
Hi Franz-Josef,
ReplyDeleteI apologize for the delay in responding, it has been one of those weeks. I'm not sure that I fully understand the question, but you should be able to supply the XML element name via an instance of JAXBElement (you would need to make your field/property of this type).
Do you have a more concrete example you could share?
-Blaise
hi, im using jaxb with json and i have useed the @XmlDescriminatorNode/@XmlDescrimintatorValue as mentioned in this post, but its not working, it always take only the super class.
ReplyDeletehere is the sample json -
{ "foo" : { "properties": [{"type":"t1", "value":"this is string"},{"type":"t2", "value":"1234"}]}}
- here properties is an array and i have class A and its subclasses X & Y are for t1 & t2 respectively.
i have used @XmlDescriminatorNode(value="type") on class A and @XmlDescriminatorValue(value="t1") on X and @XmlDescriminatorValue(value="t2") on Y respectively.
my jaxb.properties is getting read properly too.
im not able to crack this problem!! any advice on how to solve this? any work around?
thanks for the article.
also, if i make my super class as an abstract class im getting the error "Missing class indicator field from database row [UnmarshalRecord()]." - no idea!
ReplyDeleteHi Jasphior,
ReplyDeleteWhen you build your JAXBContext is it aware of both the X and Y classes? This can be done by passing these classes in when the JAXBContext is created or by using the @XmlSeeAlso annotation on the A (root) class.
-Blaise
We have the following requirement
ReplyDeleteXSD A
{
>
>
>
>
>
>
>
>
>
}
{
>
>
>
>
}
When i generate code this happens
>{
>class person
>{
> @XmlElement(name = "homeAddress", required = true)
> protected xsdb.AddressType homeAddress;
>}
>}
but we want
{
>class person
>{
> @XmlElement(name = "HomeAddress", required = true)
> protected xsda.AddressType HomeAddress;
>}
}
Just reference the class generated in common xsd and if possible even the object factory should reference xsd a object factory so it create AddressType class again.
like
{
>public xsda.AddressType createAddressType() {
> return new AddressType();
>}
}
instead of
{
>public xsdb.AddressType createAddressType() {
> return new AddressType();
>}
}
We just need reference and we arent essentially inheriting(extending the base.).I tried with inheritance plugin and could'nt achieve it. [please correct the mistakes if any]
{
>
>
>
>
>
>
>
>
> a.AddressType
>
>
}
i also worked on inheritance with child class adding nothing to base something like
in child xsd:
{
>
>
>
>
>
>
}
That too didnt work.Got compilation errors
Please let me know how it can be done.Thanks for the patience and effort.Much appreciated.
--Santhosh
Hi Santhosh,
ReplyDeleteIt appears as though content was lost in your comnment. Would you mind sending me this question through my contact me page? We can do the discussion there:
- Contact Me
-Blaise
Hey Blaise,
ReplyDeletewhat's the situation with Bug 341149 - Enhancement: Expand @XmlDescriminatorNode to support child elements?
How difficult would it be or is it even possible in the current implementation to have the XmlDescriminatorNode be an arbitrary xpath expression pointing to a discriminator value? What I would need is to point a node that is not a direct child (attribute or element) of the element for which type info is needed.
I've added a comment to the above issue describing a weird case I have.
BTW this is a very nice feature.
Regards,
Tom
Hi Tom,
DeleteWe haven't got to that enhancement yet. JSON-binding has been a big focus area for us at the moment. Here is a link to a post demonstrating how you can use an XmlAdapter to get this behaviour:
- JAXB and Inheritance - Using XmlAdapter
-Blaise
Hi Blaise, I've been trying to get the @XmlDiscriminatorNode/Value working with my XmlSchema. I've posted details to stackoverflow here:
ReplyDeletehttp://stackoverflow.com/questions/10339908/xmldiscriminatornode-xmlschema-namespace-and-elementformdefault-qualified/10340982#10340982
I've stepped through the eclipselink code and seems that something strange is going on with elementFormDefault = QUALIFIED vs UNQUALIFIED.
Hi Nick,
DeleteThis is a bug in EclipseLink JAXB (MOXy). You can track our progress on this issue using the link below. I already have a fix attached to the bug, I'll check it in once the test cases have finished running.
- http://bugs.eclipse.org/377967
-Blaise
Hi Blaise,
ReplyDeleteI need to use @XmlDiscriminatorNode/Value with @XmlValue.
I'm getting this error:
@XmlValue is not allowed on a class that derives another class.
but the @XmlValue is specific of a single type (other types get a list of elements).
Can you suggest me a solution or a work around?
Thanks very much
Giorgio
Hi Giorgio,
DeleteUsing @XmlValue shouldn't be an issue. Could you use the the "Contact Me" link at the top of the blog to email me the exception that you are getting? Could you also let me know the version of EclipseLink that you are using?
-Blaise
Hi Blaise,
ReplyDeletecan I use this approach for unmarshalling?
Yes, the same metadata works for unmarshalling as well.
Delete-Blaise
I see in the example that the child classes are provided to the JAXBContext.
ReplyDeleteJAXBContext jc = JAXBContext.newInstance(Customer.class, Address.class, PhoneNumber.class);
Is there a way to put the classes to annotation?
Or better, could MOXy scan the classpath for subclasses? Could use jandex for that to avoid performance drawbacks.
You can leverage the @XmlSeeAlso annotation for this. On the ContactInfo class you would have @XmlSeeAlso({Address.class, PhoneNumber.class}). Then you could just do JAXBContext.newInstance(Customer.class).
DeleteIt would be nice if one could re-use @XmlElements and apply it to a class.
ReplyDeleteCould you expand on this?
Delete