Chapter 37. Element Substitution Abstract XML Schema substitution groups allow you to define a group of elements that can replace a top level, or head, element. This is useful in cases where you have multiple elements that share a common base type or with elements that need to be interchangeable. 37.1. Substitution Groups in XML Schema Overview A substitution group is a feature of XML schema that allows you to specify elements that can replace another element in documents generated from that schema. The replaceable element is called the head element and must be defined in the schema's global scope. The elements of the substitution group must be of the same type as the head element or a type that is derived from the head element's type. In essence, a substitution group allows you to build a collection of elements that can be specified using a generic element. For example, if you are building an ordering system for a company that sells three types of widgets you might define a generic widget element that contains a set of common data for all three widget types. Then you can define a substitution group that contains a more specific set of data for each type of widget. In your contract you can then specify the generic widget element as a message part instead of defining a specific ordering operation for each type of widget. When the actual message is built, the message can contain any of the elements of the substitution group. Syntax Substitution groups are defined using the substitutionGroup attribute of the XML Schema element element. The value of the substitutionGroup attribute is the name of the element that the element being defined replaces. For example, if your head element is widget , adding the attribute substitutionGroup="widget" to an element named woodWidget specifies that anywhere a widget element is used, you can substitute a woodWidget element. This is shown in Example 37.1, "Using a Substitution Group" . Example 37.1. Using a Substitution Group Type restrictions The elements of a substitution group must be of the same type as the head element or of a type derived from the head element's type. For example, if the head element is of type xsd:int all members of the substitution group must be of type xsd:int or of a type derived from xsd:int . You can also define a substitution group similar to the one shown in Example 37.2, "Substitution Group with Complex Types" where the elements of the substitution group are of types derived from the head element's type. Example 37.2. Substitution Group with Complex Types The head element of the substitution group, widget , is defined as being of type widgetType . Each element of the substitution group extends widgetType to include data that is specific to ordering that type of widget. Based on the schema in Example 37.2, "Substitution Group with Complex Types" , the part elements in Example 37.3, "XML Document using a Substitution Group" are valid. Example 37.3. XML Document using a Substitution Group Abstract head elements You can define an abstract head element that can never appear in a document produced using your schema. Abstract head elements are similar to abstract classes in Java because they are used as the basis for defining more specific implementations of a generic class. Abstract heads also prevent the use of the generic element in the final product. You declare an abstract head element by setting the abstract attribute of an element element to true , as shown in Example 37.4, "Abstract Head Definition" . Using this schema, a valid review element can contain either a positiveComment element or a negativeComment element, but cannot contain a comment element. Example 37.4. Abstract Head Definition 37.2. Substitution Groups in Java Overview Apache CXF, as specified in the JAXB specification, supports substitution groups using Java's native class hierarchy in combination with the ability of the JAXBElement class' support for wildcard definitions. Because the members of a substitution group must all share a common base type, the classes generated to support the elements' types also share a common base type. In addition, Apache CXF maps instances of the head element to JAXBElement<? extends T> properties. Generated object factory methods The object factory generated to support a package containing a substitution group has methods for each of the elements in the substitution group. For each of the members of the substitution group, except for the head element, the @XmlElementDecl annotation decorating the object factory method includes two additional properties, as described in Table 37.1, "Properties for Declaring a JAXB Element is a Member of a Substitution Group" . Table 37.1. Properties for Declaring a JAXB Element is a Member of a Substitution Group Property Description substitutionHeadNamespace Specifies the namespace where the head element is defined. substitutionHeadName Specifies the value of the head element's name attribute. The object factory method for the head element of the substitution group's @XmlElementDecl contains only the default namespace property and the default name property. In addition to the element instantiation methods, the object factory contains a method for instantiating an object representing the head element. If the members of the substitution group are all of complex types, the object factory also contains methods for instantiating instances of each complex type used. Example 37.5, "Object Factory Method for a Substitution Group" shows the object factory method for the substitution group defined in Example 37.2, "Substitution Group with Complex Types" . Example 37.5. Object Factory Method for a Substitution Group Substitution groups in interfaces If the head element of a substitution group is used as a message part in one of an operation's messages, the resulting method parameter will be an object of the class generated to support that element. It will not necessarily be an instance of the JAXBElement<? extends T> class. The runtime relies on Java's native type hierarchy to support the type substitution, and Java will catch any attempts to use unsupported types. To ensure that the runtime knows all of the classes needed to support the element substitution, the SEI is decorated with the @XmlSeeAlso annotation. This annotation specifies a list of classes required by the runtime for marshalling. Fore more information on using the @XmlSeeAlso annotation see Section 32.4, "Adding Classes to the Runtime Marshaller" . Example 37.7, "Generated Interface Using a Substitution Group" shows the SEI generated for the interface shown in Example 37.6, "WSDL Interface Using a Substitution Group" . The interface uses the substitution group defined in Example 37.2, "Substitution Group with Complex Types" . Example 37.6. WSDL Interface Using a Substitution Group Example 37.7. Generated Interface Using a Substitution Group The SEI shown in Example 37.7, "Generated Interface Using a Substitution Group" lists the object factory in the @XmlSeeAlso annotation. Listing the object factory for a namespace provides access to all of the generated classes for that namespace. Substitution groups in complex types When the head element of a substitution group is used as an element in a complex type, the code generator maps the element to a JAXBElement<? extends T> property. It does not map it to a property containing an instance of the generated class generated to support the substitution group. For example, the complex type defined in Example 37.8, "Complex Type Using a Substitution Group" results in the Java class shown in Example 37.9, "Java Class for a Complex Type Using a Substitution Group" . The complex type uses the substitution group defined in Example 37.2, "Substitution Group with Complex Types" . Example 37.8. Complex Type Using a Substitution Group Example 37.9. Java Class for a Complex Type Using a Substitution Group Setting a substitution group property How you work with a substitution group depends on whether the code generator mapped the group to a straight Java class or to a JAXBElement<? extends T> class. When the element is simply mapped to an object of the generated value class, you work with the object the same way you work with other Java objects that are part of a type hierarchy. You can substitute any of the subclasses for the parent class. You can inspect the object to determine its exact class, and cast it appropriately. The JAXB specification recommends that you use the object factory methods for instantiating objects of the generated classes. When the code generators create a JAXBElement<? extends T> object to hold instances of a substitution group, you must wrap the element's value in a JAXBElement<? extends T> object. The best method to do this is to use the element creation methods provided by the object factory. They provide an easy means for creating an element based on its value. Example 37.10, "Setting a Member of a Substitution Group" shows code for setting an instance of a substitution group. Example 37.10. Setting a Member of a Substitution Group The code in Example 37.10, "Setting a Member of a Substitution Group" does the following: Instantiates an object factory. Instantiates a PlasticWidgetType object. Instantiates a JAXBElement<PlasticWidgetType> object to hold a plastic widget element. Instantiates a WidgetOrderInfo object. Sets the WidgetOrderInfo object's widget to the JAXBElement object holding the plastic widget element. Getting the value of a substitution group property The object factory methods do not help when extracting the element's value from a JAXBElement<? extends T> object. You must to use the JAXBElement<? extends T> object's getValue() method. The following options determine the type of object returned by the getValue() method: Use the isInstance() method of all the possible classes to determine the class of the element's value object. Use the JAXBElement<? extends T> object's getName() method to determine the element's name. The getName() method returns a QName. Using the local name of the element, you can determine the proper class for the value object. Use the JAXBElement<? extends T> object's getDeclaredType() method to determine the class of the value object. The getDeclaredType() method returns the Class object of the element's value object. Warning There is a possibility that the getDeclaredType() method will return the base class for the head element regardless of the actual class of the value object. Example 37.11, "Getting the Value of a Member of the Substitution Group" shows code retrieving the value from a substitution group. To determine the proper class of the element's value object the example uses the element's getName() method. Example 37.11. Getting the Value of a Member of the Substitution Group 37.3. Widget Vendor Example 37.3.1. Widget Ordering Interface This section shows an example of substitution groups being used in Apache CXF to solve a real world application. A service and consumer are developed using the widget substitution group defined in Example 37.2, "Substitution Group with Complex Types" . The service offers two operations: checkWidgets and placeWidgetOrder . Example 37.12, "Widget Ordering Interface" shows the interface for the ordering service. Example 37.12. Widget Ordering Interface Example 37.13, "Widget Ordering SEI" shows the generated Java SEI for the interface. Example 37.13. Widget Ordering SEI Note Because the example only demonstrates the use of substitution groups, some of the business logic is not shown. 37.3.2. The checkWidgets Operation Overview checkWidgets is a simple operation that has a parameter that is the head member of a substitution group. This operation demonstrates how to deal with individual parameters that are members of a substitution group. The consumer must ensure that the parameter is a valid member of the substitution group. The service must properly determine which member of the substitution group was sent in the request. Consumer implementation The generated method signature uses the Java class supporting the type of the substitution group's head element. Because the member elements of a substitution group are either of the same type as the head element or of a type derived from the head element's type, the Java classes generated to support the members of the substitution group inherit from the Java class generated to support the head element. Java's type hierarchy natively supports using subclasses in place of the parent class. Because of how Apache CXF generates the types for a substitution group and Java's type hierarchy, the client can invoke checkWidgets() without using any special code. When developing the logic to invoke checkWidgets() you can pass in an object of one of the classes generated to support the widget substitution group. Example 37.14, "Consumer Invoking checkWidgets() " shows a consumer invoking checkWidgets() . Example 37.14. Consumer Invoking checkWidgets() Service implementation The service's implementation of checkWidgets() gets a widget description as a WidgetType object, checks the inventory of widgets, and returns the number of widgets in stock. Because all of the classes used to implement the substitution group inherit from the same base class, you can implement checkWidgets() without using any JAXB specific APIs. All of the classes generated to support the members of the substitution group for widget extend the WidgetType class. Because of this fact, you can use instanceof to determine what type of widget was passed in and simply cast the widgetPart object into the more restrictive type if appropriate. Once you have the proper type of object, you can check the inventory of the right kind of widget. Example 37.15, "Service Implementation of checkWidgets() " shows a possible implementation. Example 37.15. Service Implementation of checkWidgets() 37.3.3. The placeWidgetOrder Operation Overview placeWidgetOrder uses two complex types containing the substitution group. This operation demonstrates to use such a structure in a Java implementation. Both the consumer and the service must get and set members of a substitution group. Consumer implementation To invoke placeWidgetOrder() the consumer must construct a widget order containing one element of the widget substitution group. When adding the widget to the order, the consumer should use the object factory methods generated for each element of the substitution group. This ensures that the runtime and the service can correctly process the order. For example, if an order is being placed for a plastic widget, the ObjectFactory.createPlasticWidget() method is used to create the element before adding it to the order. Example 37.16, "Setting a Substitution Group Member" shows consumer code for setting the widget property of the WidgetOrderInfo object. Example 37.16. Setting a Substitution Group Member Service implementation The placeWidgetOrder() method receives an order in the form of a WidgetOrderInfo object, processes the order, and returns a bill to the consumer in the form of a WidgetOrderBillInfo object. The orders can be for a plain widget, a plastic widget, or a wooden widget. The type of widget ordered is determined by what type of object is stored in widgetOrderForm object's widget property. The widget property is a substitution group and can contain a widget element, a woodWidget element, or a plasticWidget element. The implementation must determine which of the possible elements is stored in the order. This can be accomplished using the JAXBElement<? extends T> object's getName() method to determine the element's QName. The QName can then be used to determine which element in the substitution group is in the order. Once the element included in the bill is known, you can extract its value into the proper type of object. Example 37.17, "Implementation of placeWidgetOrder() " shows a possible implementation. Example 37.17. Implementation of placeWidgetOrder() The code in Example 37.17, "Implementation of placeWidgetOrder() " does the following: Instantiates an object factory to create elements. Instantiates a WidgetOrderBillInfo object to hold the bill. Gets the number of widgets ordered. Gets the local name of the element stored in the order. Checks to see if the element is a woodWidget element. Extracts the value of the element from the order to the proper type of object. Creates a JAXBElement<T> object placed into the bill. Sets the bill object's widget property. Sets the bill object's amountDue property.

Commands:
<element name="widget" type="xsd:string" /> <element name="woodWidget" type="xsd:string" substitutionGroup="widget" />
<complexType name="widgetType"> <sequence> <element name="shape" type="xsd:string" /> <element name="color" type="xsd:string" /> </sequence> </complexType> <complexType name="woodWidgetType"> <complexContent> <extension base="widgetType"> <sequence> <element name="woodType" type="xsd:string" /> </sequence> </extension> </complexContent> </complexType> <complexType name="plasticWidgetType"> <complexContent> <extension base="widgetType"> <sequence> <element name="moldProcess" type="xsd:string" /> </sequence> </extension> </complexContent> </complexType> <element name="widget" type="widgetType" /> <element name="woodWidget" type="woodWidgetType" substitutionGroup="widget" /> <element name="plasticWidget" type="plasticWidgetType" substitutionGroup="widget" /> <complexType name="partType"> <sequence> <element ref="widget" /> </sequence> </complexType> <element name="part" type="partType" />
<part> <widget> <shape>round</shape> <color>blue</color> </widget> </part> <part> <plasticWidget> <shape>round</shape> <color>blue</color> <moldProcess>sandCast</moldProcess> </plasticWidget> </part> <part> <woodWidget> <shape>round</shape> <color>blue</color> <woodType>elm</woodType> </woodWidget> </part>
<element name="comment" type="xsd:string" abstract="true" /> <element name="positiveComment" type="xsd:string" substitutionGroup="comment" /> <element name="negtiveComment" type="xsd:string" substitutionGroup="comment" /> <element name="review"> <complexContent> <all> <element name="custName" type="xsd:string" /> <element name="impression" ref="comment" /> </all> </complexContent> </element>
public class ObjectFactory { private final static QName _Widget_QNAME = new QName(...); private final static QName _PlasticWidget_QNAME = new QName(...); private final static QName _WoodWidget_QNAME = new QName(...); public ObjectFactory() { } public WidgetType createWidgetType() { return new WidgetType(); } public PlasticWidgetType createPlasticWidgetType() { return new PlasticWidgetType(); } public WoodWidgetType createWoodWidgetType() { return new WoodWidgetType(); } @XmlElementDecl(namespace="...", name = "widget") public JAXBElement<WidgetType> createWidget(WidgetType value) { return new JAXBElement<WidgetType>(_Widget_QNAME, WidgetType.class, null, value); } @XmlElementDecl(namespace = "...", name = "plasticWidget", substitutionHeadNamespace = "...", substitutionHeadName = "widget") public JAXBElement<PlasticWidgetType> createPlasticWidget(PlasticWidgetType value) { return new JAXBElement<PlasticWidgetType>(_PlasticWidget_QNAME, PlasticWidgetType.class, null, value); } @XmlElementDecl(namespace = "...", name = "woodWidget", substitutionHeadNamespace = "...", substitutionHeadName = "widget") public JAXBElement<WoodWidgetType> createWoodWidget(WoodWidgetType value) { return new JAXBElement<WoodWidgetType>(_WoodWidget_QNAME, WoodWidgetType.class, null, value); } }
<message name="widgetMessage"> <part name="widgetPart" element="xsd1:widget" /> </message> <message name="numWidgets"> <part name="numInventory" type="xsd:int" /> </message> <message name="badSize"> <part name="numInventory" type="xsd:int" /> </message> <portType name="orderWidgets"> <operation name="placeWidgetOrder"> <input message="tns:widgetOrder" name="order" /> <output message="tns:widgetOrderBill" name="bill" /> <fault message="tns:badSize" name="sizeFault" /> </operation> <operation name="checkWidgets"> <input message="tns:widgetMessage" name="request" /> <output message="tns:numWidgets" name="response" /> </operation> </portType>
@WebService(targetNamespace = "...", name = "orderWidgets") @XmlSeeAlso({com.widgetvendor.types.widgettypes.ObjectFactory.class}) public interface OrderWidgets { @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) @WebResult(name = "numInventory", targetNamespace = "", partName = "numInventory") @WebMethod public int checkWidgets( @WebParam(partName = "widgetPart", name = "widget", targetNamespace = "...") com.widgetvendor.types.widgettypes.WidgetType widgetPart ); }
<complexType name="widgetOrderInfo"> <sequence> <element name="amount" type="xsd:int"/> <element ref="xsd1:widget"/> </sequence> </complexType>
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "widgetOrderInfo", propOrder = {"amount","widget",}) public class WidgetOrderInfo { protected int amount; @XmlElementRef(name = "widget", namespace = "...", type = JAXBElement.class) protected JAXBElement<? extends WidgetType> widget; public int getAmount() { return amount; } public void setAmount(int value) { this.amount = value; } public JAXBElement<? extends WidgetType> getWidget() { return widget; } public void setWidget(JAXBElement<? extends WidgetType> value) { this.widget = ((JAXBElement<? extends WidgetType> ) value); } }
ObjectFactory of = new ObjectFactory(); PlasticWidgetType pWidget = of.createPlasticWidgetType(); pWidget.setShape = "round'; pWidget.setColor = "green"; pWidget.setMoldProcess = "injection"; JAXBElement<PlasticWidgetType> widget = of.createPlasticWidget(pWidget); WidgetOrderInfo order = of.createWidgetOrderInfo(); order.setWidget(widget);
String elementName = order.getWidget().getName().getLocalPart(); if (elementName.equals("woodWidget") { WoodWidgetType widget=order.getWidget().getValue(); } else if (elementName.equals("plasticWidget") { PlasticWidgetType widget=order.getWidget().getValue(); } else { WidgetType widget=order.getWidget().getValue(); }
<message name="widgetOrder"> <part name="widgetOrderForm" type="xsd1:widgetOrderInfo"/> </message> <message name="widgetOrderBill"> <part name="widgetOrderConformation" type="xsd1:widgetOrderBillInfo"/> </message> <message name="widgetMessage"> <part name="widgetPart" element="xsd1:widget" /> </message> <message name="numWidgets"> <part name="numInventory" type="xsd:int" /> </message> <portType name="orderWidgets"> <operation name="placeWidgetOrder"> <input message="tns:widgetOrder" name="order"/> <output message="tns:widgetOrderBill" name="bill"/> </operation> <operation name="checkWidgets"> <input message="tns:widgetMessage" name="request" /> <output message="tns:numWidgets" name="response" /> </operation> </portType>
@WebService(targetNamespace = "http://widgetVendor.com/widgetOrderForm", name = "orderWidgets") @XmlSeeAlso({com.widgetvendor.types.widgettypes.ObjectFactory.class}) public interface OrderWidgets { @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) @WebResult(name = "numInventory", targetNamespace = "", partName = "numInventory") @WebMethod public int checkWidgets( @WebParam(partName = "widgetPart", name = "widget", targetNamespace = "http://widgetVendor.com/types/widgetTypes") com.widgetvendor.types.widgettypes.WidgetType widgetPart ); @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) @WebResult(name = "widgetOrderConformation", targetNamespace = "", partName = "widgetOrderConformation") @WebMethod public com.widgetvendor.types.widgettypes.WidgetOrderBillInfo placeWidgetOrder( @WebParam(partName = "widgetOrderForm", name = "widgetOrderForm", targetNamespace = "") com.widgetvendor.types.widgettypes.WidgetOrderInfo widgetOrderForm ) throws BadSize; }
System.out.println("What type of widgets do you want to order?"); System.out.println("1 - Normal"); System.out.println("2 - Wood"); System.out.println("3 - Plastic"); System.out.println("Selection [1-3]"); String selection = reader.readLine(); String trimmed = selection.trim(); char widgetType = trimmed.charAt(0); switch (widgetType) { case '1': { WidgetType widget = new WidgetType(); break; } case '2': { WoodWidgetType widget = new WoodWidgetType(); break; } case '3': { PlasticWidgetType widget = new PlasticWidgetType(); break; } default : System.out.println("Invaid Widget Selection!!"); } proxy.checkWidgets(widgets);
public int checkWidgets(WidgetType widgetPart) { if (widgetPart instanceof WidgetType) { return checkWidgetInventory(widgetType); } else if (widgetPart instanceof WoodWidgetType) { WoodWidgetType widget = (WoodWidgetType)widgetPart; return checkWoodWidgetInventory(widget); } else if (widgetPart instanceof PlasticWidgetType) { PlasticWidgetType widget = (PlasticWidgetType)widgetPart; return checkPlasticWidgetInventory(widget); } }
ObjectFactory of = new ObjectFactory(); WidgetOrderInfo order = new of.createWidgetOrderInfo(); System.out.println(); System.out.println("What color widgets do you want to order?"); String color = reader.readLine(); System.out.println(); System.out.println("What shape widgets do you want to order?"); String shape = reader.readLine(); System.out.println(); System.out.println("What type of widgets do you want to order?"); System.out.println("1 - Normal"); System.out.println("2 - Wood"); System.out.println("3 - Plastic"); System.out.println("Selection [1-3]"); String selection = reader.readLine(); String trimmed = selection.trim(); char widgetType = trimmed.charAt(0); switch (widgetType) { case '1': { WidgetType widget = of.createWidgetType(); widget.setColor(color); widget.setShape(shape); JAXB<WidgetType> widgetElement = of.createWidget(widget); order.setWidget(widgetElement); break; } case '2': { WoodWidgetType woodWidget = of.createWoodWidgetType(); woodWidget.setColor(color); woodWidget.setShape(shape); System.out.println(); System.out.println("What type of wood are your widgets?"); String wood = reader.readLine(); woodWidget.setWoodType(wood); JAXB<WoodWidgetType> widgetElement = of.createWoodWidget(woodWidget); order.setWoodWidget(widgetElement); break; } case '3': { PlasticWidgetType plasticWidget = of.createPlasticWidgetType(); plasticWidget.setColor(color); plasticWidget.setShape(shape); System.out.println(); System.out.println("What type of mold to use for your widgets?"); String mold = reader.readLine(); plasticWidget.setMoldProcess(mold); JAXB<WidgetType> widgetElement = of.createPlasticWidget(plasticWidget); order.setPlasticWidget(widgetElement); break; } default : System.out.println("Invaid Widget Selection!!"); }
public com.widgetvendor.types.widgettypes.WidgetOrderBillInfo placeWidgetOrder(WidgetOrderInfo widgetOrderForm) { ObjectFactory of = new ObjectFactory(); WidgetOrderBillInfo bill = new WidgetOrderBillInfo() // Copy the shipping address and the number of widgets // ordered from widgetOrderForm to bill int numOrdered = widgetOrderForm.getAmount(); String elementName = widgetOrderForm.getWidget().getName().getLocalPart(); if (elementName.equals("woodWidget") { WoodWidgetType widget=order.getWidget().getValue(); buildWoodWidget(widget, numOrdered); // Add the widget info to bill JAXBElement<WoodWidgetType> widgetElement = of.createWoodWidget(widget); bill.setWidget(widgetElement); float amtDue = numOrdered * 0.75; bill.setAmountDue(amtDue); } else if (elementName.equals("plasticWidget") { PlasticWidgetType widget=order.getWidget().getValue(); buildPlasticWidget(widget, numOrdered); // Add the widget info to bill JAXBElement<PlasticWidgetType> widgetElement = of.createPlasticWidget(widget); bill.setWidget(widgetElement); float amtDue = numOrdered * 0.90; bill.setAmountDue(amtDue); } else { WidgetType widget=order.getWidget().getValue(); buildWidget(widget, numOrdered); // Add the widget info to bill JAXBElement<WidgetType> widgetElement = of.createWidget(widget); bill.setWidget(widgetElement); float amtDue = numOrdered * 0.30; bill.setAmountDue(amtDue); } return(bill); }