• 沒有找到結果。

Advanced Schema Reusability

在文檔中 Building Web Services with Java (頁 95-103)

The previous section demonstrated how you can reuse types and elements as is from the same or a different namespace.This capability can go a long way in some cases, but many real-world scenarios require more sophisticated reuse capabilities. Consider, for example, the format of the invoice that SkatesTown will send to the Skateboard Warehouse based on its PO (see Listing 2.25).

Listing 2.23 Continued

71 XML Schemas

Listing 2.25 SkatesTown Invoice Document

<?xml version=”1.0” encoding=”UTF-8”?>

<invoice:invoice xmlns:invoice=”http://www.skatestown.com/ns/invoice”

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=”http://www.skatestown.com/ns/invoice

http://www.skatestown.com/schema/invoice.xsd id=”43871” submitted=”2004-01-05” customerId=”73852”>

<billTo id=”addr-1”>

<company>The Skateboard Warehouse</company>

<street>One Warehouse Park</street>

<street>Building 17</street>

<city>Boston</city>

<state>MA</state>

<postalCode>01775</postalCode>

</billTo>

<shipTo href=”addr-1”/>

<order>

<item sku=”318-BP” quantity=”5” unitPrice=”49.95”>

<description>Skateboard backpack; five pockets</description>

</item>

<item sku=”947-TI” quantity=”12” unitPrice=”129.00”>

<description>Street-style titanium skateboard.</description>

</item>

<item sku=”008-PR” quantity=”1000” unitPrice=”0.00”>

<description>Promotional: SkatesTown stickers</description>

</item>

</order>

<tax>89.89</tax>

<shippingAndHandling>200</shippingAndHandling>

<totalCost>2087.64</totalCost>

</invoice:invoice>

The invoice document has many of the features of a PO document, with a few impor-tant changes:

n Invoices use a different namespace:http://www.skatestown.com/ns/invoice.

n The root element of the document is invoice, notpo.

n Theinvoiceelement has three additional children:tax,shippingAndHandling, andtotalCost.

n Theitemelement has an additional attribute:unitPrice.

How can we leverage the work done to define the PO schema in defining the invoice schema? This section will introduce the advanced schema reusability mechanisms that make this possible.

Design Principles

Imagine that purchase orders, addresses, and items were represented as classes in an object-oriented programming language such as Java.We could create an invoice object by subclassingitemtoinvoiceItem(which adds unitPrice) and potoinvoice(which addstax,shippingAndHandling, andtotalCost).The benefit of this approach is that any changes to related classes such as addresswill be automatically picked up by both POs and invoices. Further, any changes in base types such as itemwill be automatically picked up by derived types such as invoiceItem.

The following pseudocode shows how this approach might work:

public class Address { ... } public class Item

{

public String sku;

public int quantity;

}

public class InvoiceItem extends Item {

public double unitPrice;

}

public class PO {

public int id;

public Calendar submitted;

public int customerId;

public Address billTo;

public Address shipTo;

public Item order[];

}

public class Invoice extends PO {

public double tax;

public double shippingAndHandling;

public double totalCost;

}

Everything looks good except for one important detail.You might have noticed that

Invoiceshouldn’t subclass PO, because the orderarray inside an invoiceobject must holdInvoiceItems and not just Item.The subclassing relationship will force you to work with Items instead of InvoiceItems. Doing so will weaken static type-checking and require constant downcasting, which is generally a bad thing in well-designed object-oriented systems. A better design for the Invoiceclass, unfortunately, requires some duplication of PO’s data members:

73 XML Schemas

public class Invoice {

public int id;

public Calendar submitted;

public int customerId;

public Address billTo;

public Address shipTo;

public InvoiceItem order[];

public double tax;

public double shippingAndHandling;

public double totalCost;

}

Note that subclassing Itemto get InvoiceItemis a good decision because InvoiceItem

is a pure extension of Item. It adds new data members; it doesn’t require modifications toItem’s data members, nor does it change the way they’re used.

Extensions and Restrictions

The analysis from object-oriented systems can be directly applied to the design of SkatesTown’s invoice schema.The schema defines the invoiceelement in terms of pre-existing types such as addressType, and the invoice’s itemtype reuses the already-defined purchase order item type via extension (see Listing 2.26).

Listing 2.26 SkatesTown Invoice Schema

<?xml version=”1.0” encoding=”UTF-8”?>

<xsd:schema xmlns=”http://www.skatestown.com/ns/invoice”

targetNamespace=”http://www.skatestown.com/ns/invoice”

xmlns:xsd=”http://www.w3.org/2001/XMLSchema”

xmlns:po=”http://www.skatestown.com/ns/po”>

<xsd:import namespace=”http://www.skatestown.com/ns/po”

schemaLocation=”http://www.skatestown.cm/schema/po.xsd”/>

<xsd:annotation>

<xsd:documentation xml:lang=”en”>

Invoice schema for SkatesTown.

</xsd:documentation>

</xsd:annotation>

<xsd:element name=”invoice” type=”invoiceType”/>

<xsd:complexType name=”invoiceType”>

<xsd:sequence>

<xsd:element name=”billTo” type=”po:addressType”/>

<xsd:element name=”shipTo” type=”po:addressType”/>

<xsd:element name=”order”>

<xsd:complexType>

<xsd:sequence>

<xsd:element name=”item” type=”itemType”

maxOccurs=”unbounded”/>

</xsd:sequence>

</xsd:complexType>

</xsd:element>

<xsd:element name=”tax” type=”priceType”/>

<xsd:element name=”shippingAndHandling” type=”priceType”/>

<xsd:element name=”totalCost” type=”priceType”/>

</xsd:sequence>

<xsd:attribute name=”id” use=”required”

type=”xsd:positiveInteger”/>

<xsd:attribute name=”submitted” use=”required”

type=”xsd:date”/>

<xsd:attribute name=”customerId” use=”required”

type=”xsd:positiveInteger”/>

</xsd:complexType>

<xsd:complexType name=”itemType”>

<xsd:complexContent>

<xsd:extension base=”po:itemType”>

<xsd:attribute name=”unitPrice” use=”required”

type=”priceType”/>

</xsd:extension>

</xsd:complexContent>

</xsd:complexType>

<xsd:simpleType name=”priceType”>

<xsd:restriction base=”xsd:decimal”>

<xsd:minInclusive value=”0”/>

</xsd:restriction>

</xsd:simpleType>

</xsd:schema>

By now the schema mechanics should be familiar.The beginning of the schema declares the PO and invoice namespaces.The PO schema has to be imported because it doesn’t reside in the same namespace as the invoice schema.

TheinvoiceTypeschema address type is defined in terms of po:addressType, but theorderelement’s content is of type itemTypeand not po:itemType.That’s because the invoice’s itemTypeneeds to extend po:itemTypeand add the unitPriceattribute.

This happens at the next complex type definition. In general, the schema extension syn-tax, although somewhat verbose, is easy to use:

Listing 2.26 Continued

75 XML Schemas

<xsd:complexType name=”...”>

<xsd:complexContent>

<xsd:extension base=”...”>

<!-- Optional extension content model -->

<!-- Optional extension attributes -->

</xsd:extension>

</xsd:complexContent>

</xsd:complexType>

The content model of extended types contains all the child elements of the base type plus any additional elements added by the extension. Any attributes in the extension are added to the attribute set of the base type.

Last but not least, the invoice schema defines a simple price type as a non-negative decimal number.The definition happens via restriction of the lower boundary of the decimal type using the same mechanism introduced in the section on simple types.

The restriction mechanism in schemas applies not only to simple types but also to complex types.The syntax is similar to that of extension:

<xsd:complexType name=”...”>

<xsd:complexContent>

<xsd:restriction base=”...”>

<!-- Content model and attributes -->

</xsd:restriction>

</xsd:complexContent>

</xsd:complexType>

The concept of restriction has a precise meaning in XML Schema.The declarations of the type derived by restriction are very close to those of the base type but more limited.

There are several possible types of restrictions:

n Multiplicity restrictions

n Deletion of optional elements

n Tighter limits on occurrence constraints

n Provision of default values

n Provision of types where there were none, or narrowing of types

For example, we can extend the address type by restriction to create a corporate address that doesn’t include a name:

<xsd:complexType name=”corporateAddressType”>

<xsd:complexContent>

<xsd:restriction base=”addressType”>

<xsd:sequence>

<!-- Add maxOccurs=”0” to delete optional name element -->

<xsd:element name=”name” type=”xsd:string”

minOccurs=”0” maxOccurs=”0”/>

<!-- The rest is the same as in addressType -->

<xsd:element name=”company” type=”xsd:string”

minOccurs=”0”/>

<xsd:element name=”street” type=”xsd:string”

maxOccurs=”unbounded”/>

<xsd:element name=”city” type=”xsd:string”/>

<xsd:element name=”state” type=”xsd:string”

minOccurs=”0”/>

<xsd:element name=”postalCode” type=”xsd:string”

minOccurs=”0”/>

<xsd:element name=”country” type=”xsd:string”

minOccurs=”0”/>

</xsd:sequence>

<xsd:attribute name=”id” type=”xsd:ID”/>

<xsd:attribute name=”href” type=”xsd:IDREF”/>

</xsd:restriction>

</xsd:complexContent>

</xsd:complexType>

The Importance of xsi:type

The nature of restriction is such that an application that is prepared to deal with the base type can certainly accept the derived type. In other words, you can use a corporate address type directly inside the billToandshipToelements of POs and invoices with-out a problem. Sometimes, however, it might be convenient to identify the actual schema type used in an instance document. XML Schema allows you to do this through the use of the global xsi:typeattribute.This attribute can be applied to any element to signal its actual schema type, as Listing 2.27 shows.

Listing 2.27 Using xsi:type

<?xml version=”1.0” encoding=”UTF-8”?>

<po:po xmlns:po=”http://www.skatestown.com/ns/po”

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=”http://www.skatestown.com/ns/po

http://www.skatestown.com/schema/po.xsd id=”43871” submitted=”2004-01-05” customerId=”73852”>

<billTo xsi:type=”po:corporateAddressType

>

<company>The Skateboard Warehouse</company>

<street>One Warehouse Park</street>

<street>Building 17</street>

<city>Boston</city>

<state>MA</state>

<postalCode>01775</postalCode>

</billTo>

...

</po:po>

77 XML Schemas

Although derivation by restriction doesn’t require the use of xsi:type, derivation by extension often does.The reason is that an application prepared for the base schema type is unlikely to be able to process the derived type (it adds information) without a hint.

But why would such a scenario ever occur? Why would an instance document contain data from a type derived by extension in a place where the schema expects a base type?

XML Schema allows derivation by extension to be used in cases where it really shouldn’t be used, as in the case of the invoice and PO datatypes. In these cases, you must use xsi:typein the instance document to ensure successful validation. Consider a scenario where the invoice type was derived by extension from the PO type:

<xsd:complexType name=”invoiceType”>

<xsd:complexContent>

<xsd:extension base=”po:poType”>

<xsd:element name=”tax” type=”priceType”/>

<xsd:element name=”shippingAndHandling” type=”priceType”/>

<xsd:element name=”totalCost” type=”priceType”/>

</xsd:extension>

</xsd:complexContent>

</xsd:complexType>

Remember, extension doesn’t change the content model of the base type; it can only add to the content model.Therefore, this definition will make the itemelement inside invoices of type po:itemType, notinvoice:itemType.The use of xsi:type(see Listing 2.28) is the only way to add unit prices to items without violating the validity con-straints of the document imposed by the schema. An imperfect analogy from program-ming languages is that xsi:typeprovides the true type to downcast to when you’re holding a reference to a base type.

Listing 2.28 Using xsi:type to Correctly Identify Invoice Item Elements

<order>

<item sku=”318-BP” quantity=”5” unitPrice=”49.95”

xsi:type=”invoice:itemType”>

<description>Skateboard backpack; five pockets</description>

</item>

<item sku=”947-TI” quantity=”12” unitPrice=”129.00”

xsi:type=”invoice:itemType”>

<description>Street-style titanium skateboard.</description>

</item>

<item sku=”008-PR” quantity=”1000” unitPrice=”0.00”

xsi:type=”invoice:itemType”>

<description>Promotional: SkatesTown stickers</description>

</item>

</order>

This example shows a use of xsi:typethat comes as a result of poor schema design. If, instead of extending PO, the invoice type is defined on its own, the need for xsi:type disappears. However, sometimes even good schema design doesn’t prevent the need to identify actual types in instance documents.

Imagine that, due to constant typos in shipping and billing address postal codes, SkatesTown decides to become more restrictive in its document validation.The company defines three types of addresses that are part of POs and schema.The types have the fol-lowing constraints:

n Address—Same as always

n USAddress—Country isn’t allowed, and the ZIP Code pattern “\d{5}(-\d{4})?” is enforced

n UKAddress—Country is fixed to UK, and the postal code pattern “[0-9A-Z]{3}

[0-9A-Z]{3}” is enforced

To get the best possible validation, SkatesTown’s applications need to know the exact type of address that is being used in a document.Without using xsi:type, the PO and invoice schema will each have to define nine (three squared) possible combinations of

billToandshipToelements:billTo/shipTo,billTo/shipToUS,billTo/shipToUK,

billToUS/shipTo, and so on. It’s better to stick with billToandshipToand use

xsi:typeto get exact schema type information.

在文檔中 Building Web Services with Java (頁 95-103)