Contents


Web services tip

Use polymorphism as an alternative to xsd:choice

What you can do when the mapping of xsd:choice is unusable

Comments

Content series:

This content is part # of # in the series: Web services tip

Stay tuned for additional content in this series.

This content is part of the series:Web services tip

Stay tuned for additional content in this series.

xsd:choice is a somewhat common XML construct. However, mappings of xsd:choice to a programming language are not always straightforward. For example, the Java mapping of Web services, defined by the JAX-RPC specification, does not provide an explicit mapping from xsd:choice into Java. Whenever a JAX-RPC code generator encounters xsd:choice in a type definition, it maps that type to javax.xml.soap.SOAPElement. SOAPElement, part of the SAAJ API, is not a very user-friendly API.

You can do something to get a better API. A good option, if you're able to change your XML schema, is to replace xsd:choice with polymorphism.

Compare xsd:choice and polymorphism

Polymorphism means "many shapes." A method parameter, for instance, can be declared with a base type. When calling that method, an instance of that parameter is a specific extension of that type.

You could describe xsd:choice similarly. A method parameter can be declared with a type which describes all possible choices. When calling that method, an instance of that parameter will contain a specific choice.

The actual words are different, but their meanings are essentially the same.

Map simple xsd:choice types to polymorphic types

To convert a typical choice type to a set of polymorphic types, you need to do the following, given choice type C, containing choices c1..cn:

  1. Build an abstract type P containing those elements of C, which are not choice elements.
  2. For each c in c1..cn, build an extension type E of P which contains the elements of c.

It's always easier to understand a concept with an example. Take a look at declarations of payment options. Listing 1 is the choice type. Listing 2 is the conversion to a set of polymorphic types.

Listing 1. Choice payment
<complexType name="Payment">
  <sequence>
    <element name="amount" type="int"/>
    <choice>
      <element name="cash" nillable="true" type="string"/>
      <element name="check" type="tns:Check"/>
      <element name="credit" type="tns:Credit"/>
    </choice>
  </sequence>
</complexType>

<complexType name="Check">
  <sequence>
    <element name="checkNo" type="int"/>
  </sequence>
</complexType>
<complexType name="Credit">
  <sequence>
    <element name="cardNo" type="string"/>
    <element name="expiration" type="date"/>
  </sequence>
</complexType>
Listing 2. Polymorphic payment
<complexType abstract="true" name="Payment">
  <sequence>
    <element name="amount" type="int"/>
  </sequence>
</complexType>

<complexType name="CashPayment">
  <complexContent>
    <extension base="tns:Payment">
      <sequence>
        <element name="cash" nillable="true" type="string"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

<complexType name="CheckPayment">
  <complexContent>
    <extension base="tns:Payment">
      <sequence>
        <element name="check" type="tns:Check"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

<complexType name="CreditPayment">
  <complexContent>
    <extension base="tns:Payment">
      <sequence>
        <element name="credit" type="tns:Credit"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

The polymorphic payment example is more verbose than the choice payment example. That's because XML is not really an object-oriented language, and applying object-oriented features to it is not straightforward. But that should not deter you. Most likely you will map XML to a programming language. As a developer in that particular programming language, you don't need to care what the XML schema looks like; you only need to care about what the language mapping of that schema looks like.

The only time you should care what the XML schema looks like is when you want to do performance analysis. For instance, if this schema is part of a Web service, you want to compare the Simple Object Access Protocol (SOAP) messages for these types in order to determine whether the message size itself affects performance. So let's compare the instances of these two variations. For example, assume we want the payment to be by check (number 1050) for the amount of $10 USD. Listing 3 shows the choice example and Listing 4 shows the polymorphic example. (Note that I am ignoring namespaces and prefixes to simplify this example.)

Listing 3. Choice payment instance
<payment>
  <amount>10</amount>
  <check>
    <checkNo>1050</checkNo>
  </check>
</payment>
Listing 4. Polymorphic payment instance
<payment xsi:type="CheckPayment">
  <amount>10</amount>
  <check>
    <checkNo>1050</checkNo>
  </check>
</payment>

As you can see, the only difference between Listing 4 and Listing 5 is that the polymorphic instance contains type information. While this is noteworthy, it's not typically significant. In fact, it can potentially simplify a SOAP engine's processing because the engine knows, right from the beginning, what the type is and selects the appropriate deserializer up front, rather than having to parse the instance and to inspect the name of the choice element -- in this case, check -- to determine the type.

So using the polymorphic alternate is not significantly more expensive than the choice alternate, and it can provide you with a much more user-friendly language mapping.

Map xsd:choice with maxOccurs greater than 1

The vast majority of choice scenarios are similar to the the simple case shown above. But a choice could have a maxOccurs attribute greater than 1. The mapping is similar, but you need an extra layer to handle the new array aspect of this new type.

  1. Build an empty abstract type P.
  2. Replace the choice from C with an element of type P, carrying the maxOccurs attribute from the choice to the new element.
  3. For each c in c1..cn, build an extension type E of P, which contains the elements of c.

For example, modify the Payment type from Listing 1 to include a maxOccurs="unbounded" attribute on the choice. (See Listing 5, with the addition highlighted in bold.)

(What this type means is that a person could make a single payment using multiple payment options: part by cash, part by check, and part by credit card; or any combination.)

Listing 5. Choice payment with maxOccurs
<complexType name="Payment">
  <sequence>
    <element name="amount" type="int"/>
    <choice maxOccurs="unbounded">
      <element name="cash" nillable="true" type="string"/>
      <element name="check" type="tns:Check"/>
      <element name="credit" type="tns:Credit"/>
    </choice>
  </sequence>
</complexType>

Listing 6 shows the converted polymorphic type set. The changes from Listing 2 are highlighted in bold.

Listing 6. Polymorphic payment with maxOccurs
<complexType name="Payment">
  <sequence>
    <element name="amount" type="int"/>
    <element name="option" type="PaymentOption" maxOccurs="unbounded"/>
  </sequence>
</complexType>

<complexType abstract="true" name="PaymentOption">
  <sequence/>
</complexType>


<complexType name="CashPayment">
  <complexContent>
    <extension base="tns:PaymentOption">
      <sequence>
        <element name="cash" nillable="true" type="string"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

<complexType name="CheckPayment">
  <complexContent>
    <extension base="tns:PaymentOption">
      <sequence>
        <element name="check" type="tns:Check"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

<complexType name="CreditPayment">
  <complexContent>
    <extension base="tns:PaymentOption">
      <sequence>
        <element name="credit" type="tns:Credit"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>

Note that the PaymentOption type has no fields of its own. That might look a bit odd, but there's nothing wrong with it. It's an abstract type, so a PaymentOption instance will never exist; only extensions of it can exist.

Note that you can also apply the maxOccurs rules to convert the typical choice example. But I hope you see the benefit of the simplified rules for the typical case: You have one less level of indirection -- one less type -- to deal with.

Advantages of polymorphism over xsd:choice

Polymorphism has two main advantages over xsd:choice: extensibility and type indication.

Extensibility

Polymorphic types, by their nature, are extensible. You can have as many extensions of the abstract base type as you want, depending on whether you spread those extensions to numerous .xsd files for organizational purposes (one organization might accept only cash and checks; another might only accept credit cards and online payments) or whether you add new ones over time.

But xsd:choice, by itself, is not extensible. If you want to add choices over time or if you want different sets of choices in different .xsd files, then either you have all possible choices defined in one monolithic type and let the code worry about what's available for each scenario, or you have many types containing identical choice options. Neither of these options is particularly robust. While it is true that you could extend a choice type and add choices to the extension type, why not take the complete leap and follow the polymorphic pattern completely, removing all xsd:choice usage?

Type indication

In this payment example, one of the choices is cash. Have you noticed that it seems a rather oddly defined choice? What do you have to know about cash other than the amount? Nothing. That is why the attribute nillable="true" was added; you could give the cash element some value, but it would be meaningless. An instance of a cash payment could look like Listing 7.

Listing 7. Choice payment cash instance
<payment>
  <amount>10</amount>
  <cash xsi:isNil="true"/>
</payment>

All you really want to say here is that this is a cash payment. But in a choice, there's no way to indicate that without a placeholder for it.

In the CashPayment type in Listing 2, I carried the cash element along because I simply applied the conversion rules, but you really don't need it. All you need is an indication that this is a cash type, nothing more, and you have that by virtue of polymorphism. So the CashPayment option would extend the Payment type, but adds nothing new of its own. Listing 8 shows the improved version of the CashPayment type. Listing 9 shows an instance of this type.

Listing 8. Improved polymorphic CashPayment
<complexType name="CashPayment">
  <complexContent>
    <extension base="tns:Payment">
      <sequence/>
    </extension>
  </complexContent>
</complexType>
Listing 9. Polymorphic payment cash instance
<payment xsi:type="CashPayment">
  <amount>10</amount>
</payment>

Summary

In some scenarios, an xsd:choice type is not the optimal type to use. For instance, when mapping a choice type to Java language using the JAX-RPC mapping rules, you end up with a Java mapping, which is not very user friendly. If you have control over your schema, you might want to convert the type containing the xsd:choice to a set of polymorphic types. Not only will you end up with a more user-friendly mapping, but polymorphism is more robust than xsd:choice.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services, XML
ArticleID=94191
ArticleTitle=Web services tip: Use polymorphism as an alternative to xsd:choice
publish-date=09202005