Web services tip: Use polymorphism as an alternative to xsd:choice

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

xsd:choice is not always the most optimal XML schema construct. For instance, a type containing xsd:choice does not map to a user-friendly Java™ class using a JAX-RPC code generator. In this article, you learn about a functional equivalent to xsd:choice: polymorphism.

Share:

Russell Butek (butek@us.ibm.com), Software Engineer, EMC

Russell Butek is an IBM Web services consultant. He has been one of the developers of the IBM WebSphere Web services engine. He is also a member the JAX-RPC Java Specification Request (JSR) expert group. He was involved in the implementation of Apache's AXIS SOAP engine, driving AXIS 1.0 to comply with JAX-RPC 1.0. Previously, he was a developer of the IBM CORBA ORB and an IBM representative on a number of OMG task forces, including chairing the portable interceptor task force. Contact Russell at butek@us.ibm.com.



20 September 2005

Introduction

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.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into SOA and web services on developerWorks


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