prev | table of contents | next |
A substitution group lets you write schema structures that reference
one element but permit the substitution of any other element from the
substitution group in an instance document. Below is a simple example,
defining a complex type for a binary arithmetic operation. The
substitution group is headed by the global operand
element, which is referenced from the group members
constant
and variable
.
<xsd:complexType name="BinopType"> <xsd:sequence> <xsd:element ref="operand"/> <xsd:element ref="operand"/> </xsd:sequence> <xsd:attribute name="operator" type="xsd:string"/> </xsd:complexType> <xsd:element name="operand" type="xsd:string"/> <xsd:element name="constant" type="xsd:string" substitutionGroup="operand"/> <xsd:element name="variable" type="xsd:string" substitutionGroup="operand"/> <xsd:element name="binop" type="BinopType"/>The benefit of this schema definition is that it permits you to create
binop
elements consisting of any combination of
constant
and variable
elements. The generated
Java code shouldn't surprise you; the field content
must
act as a portmanteau for all possible operand pairs, and that's why
we have to be content with a list.
public class BinopType { protected List<JAXBElement<String>> content; protected String operator; public List<JAXBElement<String>> getContent() { if (content == null) { content = new ArrayList<JAXBElement<String>>(); } return this.content; } public String getOperator() { return operator; } public void setOperator(String value) { this.operator = value; } }Creating an element from a substitution group is slightly more complex now because such elements have to be represented by an object from some parameterized
JAXBElement<?>
class. This is
illustrated in the Java code snippet shown below that demonstrates
the assembly of a well-known formula.
JAXBContext ctxt = JAXBContext.newInstance( "generated" ); Marshaller m = ctxt.createMarshaller(); m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true ); ObjectFactory of = new ObjectFactory(); BinopType bt = of.createBinopType(); bt.setOperator( "*" ); JAXBElement<String> op1 = of.createConstant( "3.14" ); JAXBElement<String> op2 = of.createVariable( "d" ); bt.getContent().add( op1 ); bt.getContent().add( op2 ); JAXBElement<BinopType> jbe = of.createBinop( bt ); m.marshal( jbe, System.out );And here is the resulting instance document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <binop operator="*"> <constant>3.14</constant> <variable>d</variable> </binop>
Another example illustrates the usage of a substitution group
with complex schema types. The elements of the group may have different
types, but they must be derived from the same base type, either
by restriction or by extension. A typical scenario would be the
use of a base type in the head element and various extensions for
the other elements. This is what we have in the example, where
ItemType
is the base type and BookType
and DiskType
are the subtypes. A PosType
element represents a position in an order. It is defined as
containing one element of ItemType
. This type, however,
is conceptually an abstract type, which should be expressed
by the attribute setting abstract="true"
.
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"> <xs:annotation> <xs:appinfo> <jaxb:schemaBindings> <jaxb:package name="acme.item"/> </jaxb:schemaBindings> </xs:appinfo> </xs:annotation> <xs:element name="item" type="ItemType"/> <xs:element name="book" type="BookType" substitutionGroup="item"/> <xs:element name="disk" type="DiskType" substitutionGroup="item"/> <xs:complexType name="ItemType" abstract="true"> <xs:sequence> <xs:element name="title" type="xs:string"/> <xs:element name="price" type="xs:int"/> </xs:sequence> </xs:complexType> <xs:complexType name="BookType"> <xs:complexContent> <xs:extension base="ItemType"> <xs:sequence> <xs:element name="pages" type="xs:int"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="DiskType"> <xs:complexContent> <xs:extension base="ItemType"> <xs:sequence> <xs:element name="duration" type="xs:int"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="PosType"> <xs:sequence> <xs:element ref="item"/> <xs:element name="quantity" type="xs:int"/> </xs:sequence> </xs:complexType> <xs:complexType name="OrderType"> <xs:sequence> <xs:element name="pos" type="PosType" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:element name="order" type="OrderType"/> </xs:schema>The interesting sections of the generated code are outlined below. We see the abstract base class
ItemType
with its extensions
for books and disks, and its use as the generic type parameter, where
the wildcard ?
is suitably restricted to subtypes of
ItemType
.
public abstract class ItemType { protected String title; protected int price; // ... (getters and setters) } public class BookType extends ItemType { protected int pages; // ... (getters and setters) } public class DiskType extends ItemType { protected int duration; // ... (getters and setters) } public class PosType { protected JAXBElement<? extends ItemType> item; protected int quantity; public JAXBElement<? extends ItemType> getItem() { return item; } public void setItem(JAXBElement<? extends ItemType> value) { this.item = ((JAXBElement<? extends ItemType> ) value); } // ... (more getters and setters) }Once again, element construction is a tad more complicated. The
ObjectFactory
provides create methods for a surprisingly
large number of elements. There are methods returning elements of one
of the plain types BookType
and DiskType
.
Also, we can create an element by calling a method that returns an
object of type JAXBElement
, parameterized with
BookType
or DiskType
, each of which
requires an argument of the parameter type. And finally there is
createItem
, returning an object whose type is
ItemType
. Here is the skeleton of this class:
public class ObjectFactory { public ObjectFactory() { } public BookType createBookType() { ... } public DiskType createDiskType() { ... } public OrderType createOrderType() { ... } public PosType createPosType() { ... } public JAXBElement<OrderType> createOrder(OrderType value) { ... } public JAXBElement<ItemType> createItem(ItemType value) { ... } public JAXBElement<DiskType> createDisk(DiskType value) { ... } public JAXBElement<BookType> createBook(BookType value) { ... } }The table below shows where each of these lements can be used.
Method | Result Type | Use as argument of |
---|---|---|
createBookType | BookType | createBook()
|
createDiskType | DiskType | createDisk()
|
createBook | JAXBElement<BookType> | PosType.setItem()
|
createDisk | JAXBElement<DiskType> | PosType.setItem()
|
createItem | JAXBElement<ItemType> | PosType.setItem()
|
Looking at this table, you may wonder why there are three methods to
create an item in a PosType
element. Some experimenting
exhibits that indeed all three can be used:
ObjectFactory of = new ObjectFactory(); // Create an order OrderType st = of.createOrderType(); List<PosType> listPos = st.getPos(); // Order two copies of a book. PosType p1 = of.createPosType(); listPos.add( p1 ); BookType bk = of.createBookType(); bk.setTitle( "The Joy of JAXB" ); bk.setPages( 832 ); bk.setPrice( 120 ); p1.setItem( of.createItem( bk ) ); // createItem for BookType p1.setQuantity( 2 ); // Order a disk. PosType p2 = of.createPosType(); listPos.add( p2 ); DiskType dk = of.createDiskType(); dk.setTitle( "Keyclick Calypso" ); dk.setDuration( 50 ); dk.setPrice( 20 ); p2.setItem( of.createDisk( dk ) ); // createDisk p2.setQuantity( 1 ); JAXBElement<OrderType> jbe = of.createOrder( st );The marshalled XML text shows that the generic element tag
item
can indeed be instantiated (even though
its schema type ItemType
is abstract) but at a price:
The actual type of the element has to be specified using
the XML instance attribute xsi:type="BookType"
,
along with the lengthy namespace declaration.
The subtype element tagged disk
does not
require this burden as its tag is unambiguous.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <order> <pos> <item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="BookType"> <title>Inside JAXB</title> <price>120</price> <pages>832</pages> </item> <quantity>2</quantity> </pos> <pos> <disk> <title>Keyclick Calypso</title> <price>20</price> <duration>50</duration> </disk> <quantity>1</quantity> </pos> </order>
Unmarshalling requires an additional call to get at the
value wrapped in a JAXBElement<? extends ItemType>
,
but common subelements can be accessed via calls of
ItemType
methods.
JAXBElement<?> jbe = (JAXBElement<?>)u.unmarshal( new FileInputStream( "order.xml" ) ); OrderType order = (OrderType)jbe.getValue(); for( PosType p: order.getPos() ){ ItemType item = p.getItem().getValue(); String tag = p.getItem().getName().getLocalPart(); System.out.println( item.getClass().getSimpleName() ); System.out.println( tag + " " + item.getTitle() + " " + p.getQuantity() ); }In addition to using the standard Java technique for determining an object's class we can also extract the tag by calling method
getName()
on the JAXBElement
containing the
ItemType
object. The tag is represented as an object of
class javax.xml.namespace.QName
which contains the simple
name as well as the namespace prefix.
In spite of the restrictions and the slightly more complex element construction, substitution groups are an adequate technique for representing object hierarchies.
prev | table of contents | next |