XML Schema
We will map our object model to the following XML schema that contains a choice structure. In this XML schema the choice structure means that after the name element, one of the following elements may occur: address, phone-number, or note.
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="customer" type="customer"/> <xsd:complexType name="customer"> <xsd:sequence> <xsd:element name="name" type="xsd:string" minOccurs="0"/> <xsd:choice> <xsd:element name="address" type="address"/> <xsd:element name="phone-number" type="phoneNumber"/> <xsd:element name="note" type="xsd:string"/> </xsd:choice> </xsd:sequence> </xsd:complexType> <xsd:complexType name="address"> <xsd:sequence> <xsd:element name="city" type="xsd:string" minOccurs="0"/> <xsd:element name="street" type="xsd:string" minOccurs="0"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="phoneNumber"> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="type" type="xsd:string"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:schema>
Java Model
We will use the following domain model for this example. The model represents information about a customer.
Customer
We will use the @XmlElements annotation to map to the choice structure. Inside @XmlElements are multiple @XmlElement annotations that link a element with a given name and namespace to a class. During a marshal operation the type of object held by the "contactInfo" property will dictate the element produced, and during an unmarshal operation the element information will dictate what type of object is instantiated.
package blog.choice; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Customer { private String name; @XmlElements(value = { @XmlElement(name="address", type=Address.class), @XmlElement(name="phone-number", type=PhoneNumber.class), @XmlElement(name="note", type=String.class) }) private Object contactInfo; }
Address
package blog.choice; public class Address { private String street; private String city; }
PhoneNumber
package blog.choice; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber { @XmlAttribute private String type; @XmlValue private String number; }
Demo Code
Customer with Address
First we will set an instance of Address on the "contactInfo" property:
package blog.choice; import javax.xml.bind.*; public class DemoWithAddress { public static void main(String[] args) throws Exception { Customer customer = new Customer(); customer.setName("Jane Doe"); Address address = new Address(); address.setStreet("1 A Street"); address.setCity("Any Town"); customer.setContactInfo(address); JAXBContext jc = JAXBContext.newInstance(Customer.class, PhoneNumber.class, Address.class); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
This will result in the following XML:
<customer> <name>Jane Doe</name> <address> <city>Any Town</city> <street>1 A Street</street> </address> </customer>
Customer with PhoneNumber
Next we will set an instance of PhoneNumber on the "contactInfo" property:
Next we will set an instance of PhoneNumber on the "contactInfo" property:
package blog.choice; import javax.xml.bind.*; public class DemoWithPhoneNumber { public static void main(String[] args) throws Exception { Customer customer = new Customer(); customer.setName("Jane Doe"); PhoneNumber phoneNumber = new PhoneNumber(); phoneNumber.setType("work"); phoneNumber.setNumber("555-1111"); customer.setContactInfo(phoneNumber); JAXBContext jc = JAXBContext.newInstance(Customer.class, PhoneNumber.class, Address.class); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
This will result in the following XML:
<customer> <name>Jane Doe</name> <phone-number type="work">555-1111</phone-number> </customer>
Customer with String
Finally we will set an instance of String on the "contactInfo" property:
package blog.choice; import javax.xml.bind.*; public class DemoWithString { public static void main(String[] args) throws Exception { Customer customer = new Customer(); customer.setName("Jane Doe"); customer.setContactInfo("jane.doe@example.com"); JAXBContext jc = JAXBContext.newInstance(Customer.class, PhoneNumber.class, Address.class); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
This will result in the following XML:
<customer> <name>Jane Doe</name> <note>jane.doe@example.com</note> </customer>
Further Reading
If you enjoyed this post you may also be interested in:
What about the case where two of the choices are of the same type, say phoneNumber is String and faxNumber is String as well.
ReplyDeleteIf we add @XmlElement(name="fax-number", type=PhoneNumber.class) to the @XmlElements annotation in our example the following will happen.
ReplyDelete1. The unmarshal will work as expected, both the "phone-number" and "fax-number" elements will cause a PhoneNumber object to be instanitated.
2. The marshal operation will choose one of the element names to use.
In my opinion you should be able to set your property type to JAXBElement, and use that to preserve the element name. This does not currently work in Metro or MOXy. I have entered the following bug to track this issue:
- https://bugs.eclipse.org/328646
Question about "reverse-engineering" the request where the consumer/client is working off my WSDL with the option. How do you know which one of the choices was used or if they even entered one of the choice values which are "required"?
ReplyDeleteFor "reverse-engineering" you will need to infer which choice was supplied by the resulting value on the model object. To enforce that they supplied a required choice you can set a schema on the unmarshaller that will force it to validate.
ReplyDeleteHi,
ReplyDeleteI generated the JAXB classes using the above xsd in RAD7.
but the Customer class is not same as the above..
looks choice is not woking please find the classes below.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "customer", propOrder = {
"name",
"address",
"phoneNumber",
"note"
})
public class Customer {
protected String name;
protected Address address;
@XmlElement(name = "phone-number")
protected PhoneNumber phoneNumber;
protected String note;
Do you think the problem in RAD ?
Hi Madhava,
ReplyDeleteThe behaviour you are seeing is correct. I have written a new post to better answer your question:
- XML Schema to Java - XSD Choice
-Blaise
Thanks! This was excellent and clear information. I had been googling the web to figure out how to generate the xsd choice but it was hard to find since the word "choice" also seems to be overloaded with non-XML-related semantics ;-)
ReplyDeleteAnyway, I've bookmarked you blog as a valuable resource!