prev table of contents next

2.2.14 Substitution Groups

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
createBookTypeBookType createBook()
createDiskTypeDiskType 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