Input (input.xml)
In this example the possible contact methods are Address and PhoneNumber. If the street attribute is present on the contact-method element we will instantiate an Address object, and if the number attribute is present we will instantiate a PhoneNumber object.
<?xml version="1.0" encoding="UTF-8"?> <customer> <contact-method number="555-1111"/> <contact-method street="1 A St" city = "Any Town"/> <contact-method number="555-2222"/> </customer>
Java Model
Below is the domain model that will be used for this example.
Customer
package blog.inheritance.xmlclassextractor; import java.util.List; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement(name="contact-method") private List<ContactMethod> contactMethods; }
ContactMethod
Since we are using a custom mechanism to indicate the subtype, we can use MOXy's @XmlClassExtractor extension to plug in the custom logic.
Since we are using a custom mechanism to indicate the subtype, we can use MOXy's @XmlClassExtractor extension to plug in the custom logic.
package blog.inheritance.xmlclassextractor; import javax.xml.bind.annotation.XmlSeeAlso; import org.eclipse.persistence.oxm.annotations.XmlClassExtractor; @XmlClassExtractor(ContactMethodClassExtractor.class) @XmlSeeAlso({Address.class, PhoneNumber.class}) public abstract class ContactMethod { }
Address
package blog.inheritance.xmlclassextractor; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Address extends ContactMethod { @XmlAttribute protected String street; @XmlAttribute protected String city; }
PhoneNumber
package blog.inheritance.xmlclassextractor; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber extends ContactMethod { @XmlAttribute protected String number; }
ClassExtractor (ContactMethodClassExtractor)
ContactMethodClassExtractor is an implementation of ClassExtractor. Its role is to decide which subtype should be returned based on an instance of Record. The Record class represents the XML element currently being unmarshalled. This class serves a similar role to the ContactMethodAdapter from the JAXB and Inheritance - Using XmlAdapter example but requires much less code.
package blog.inheritance.xmlclassextractor; import org.eclipse.persistence.descriptors.ClassExtractor; import org.eclipse.persistence.sessions.Record; import org.eclipse.persistence.sessions.Session; public class ContactMethodClassExtractor extends ClassExtractor{ @Override public Class extractClassFromRow(Record record, Session session) { if(null != record.get("@street")) { return Address.class; } else if(null != record.get("@number")) { return PhoneNumber.class; } return null; } }
Demo Code
The following demo code will be used for this example. We will unmarshal the input document, output the type of each object in the collection, and then marshal the objects back to XML.
package blog.inheritance.xmlclassextractor; import java.io.File; import javax.xml.bind.*; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); File xml = new File("src/blog/inheritance/xmlclassextractor/input.xml"); Customer customer = (Customer) unmarshaller.unmarshal(xml); for(ContactMethod contactMethod : customer.getContactMethods()) { System.out.println(contactMethod.getClass()); } Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
Output
The following is the output from running the demo code. Note how each of the instances of ContactMethod in the collection are of the appropriate sub-type.
class blog.inheritance.xmlclassextractor.PhoneNumber class blog.inheritance.xmlclassextractor.Address class blog.inheritance.xmlclassextractor.PhoneNumber <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customer> <contact-method number="555-1111"/> <contact-method city="Any Town" street="1 A St"/> <contact-method number="555-2222"/> </customer>
Further Reading
If you enjoyed this post, then you also be interested in:
if the subtype is an element and not a attribut like
ReplyDelete<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<contact-method>
<number>555-1111"</number>
</contact-method>
<contact-method>
<city>Any Town</city>
<street>=1 A St</street>
</contact-method>
<contact-method>
<number>555-1111"</number>
</contact-method>
</customer>
I haven't seen this example.
Currently MOXy requires that the inheritance indicator be in an XML attribute. If it is in an XML element you could use the following approach with an XmlAdapter:
Delete- JAXB and Inheritance - Using XmlAdapter
-Blaise