WSDL Essentials, Page 3
XML Schema Data Typing
In order for a SOAP client to communicate effectively with a SOAP server, the client and server must agree on a data type system. By default, XML 1.0 does not provide a data type system. In contrast, every programming language provides some basic facility for declaring data types, such as integers, floats, doubles, and strings. One of the greatest challenges in building web services is therefore creating a common data type system that can be used by a diverse set of programming languages running on a diverse set of operating systems.
WSDL does not aim to create a standard for XML data typing. In fact, WSDL is specifically designed for maximum flexibility and is therefore not tied exclusively to any one data type system. Nonetheless, WSDL does default to the W3C XML Schema specification. The XML Schema specification is also currently the most widely used specification for data typing.
The more you know about XML Schemas, the better you can understand complex WSDL files. A full discussion of XML Schemas is beyond the scope of this chapter. However, two facts are crucially important.
First, the XML Schema specification includes a basic type system
for encoding most data types. This type system includes a long list of
built-in simple types, including strings, floats, doubles, integers, time, and
date. This list, shown in Table
6-1, is excerpted from the XML Schema Part 0: Primer (http://www.w3org/TR/2000/WD=xmlschema=0=20000407/).
If your application sticks to these simple data types, there is no need to
include the WSDL types element, and the resulting
WSDL file is extremely simple. For example, our first two WSDL files use only
strings and floats.
|
Simple type |
Example(s) |
|---|---|
|
string |
Web Services |
|
Boolean |
true, false, 1, 0 |
|
-INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN | |
|
double |
-INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN |
|
decimal |
-1.23, 0, 123.4, 1000.00 |
|
binary |
100010 |
|
integer |
-126789, -1, 0, 1, 126789 |
|
nonPositiveInteger |
-126789, -1, 0 |
|
negativeInteger |
-126789, -1 |
|
long |
-1, 12678967543233 |
|
int |
-1, 126789675 |
|
short |
-1, 12678 |
|
byte |
-1, 126 |
|
nonNegativeInteger |
0, 1, 126789 |
|
unsignedLong |
0, 12678967543233 |
|
unsignedInt |
0, 1267896754 |
|
unsignedShort |
0, 12678 |
|
unsignedByte |
0, 126 |
|
positiveInteger |
1, 126789 |
|
date |
1999-05-31 |
|
time |
13:20:00.000, 13:20:00.000-05:00 |
Second, the XML Schema specification provides a facility for
creating new data types. This is important if you want
to create data types that go beyond what is already defined within the Schema.
For example, a service might return an array of floats or a more complex stock
quote object containing the high, low, and volume figures for a specific
stock. Whenever your service goes beyond the simple XML Schema data types, you
must declare these new data types within the WSDL types element.
In the next two sections of this chapter, we present two specific examples of using XML Schemas to create new data types. The first focuses on arrays; the second focuses on a more complex data type for encapsulating product information.
Arrays
Example
6-6, shown later in this section, is a sample WSDL file that illustrates
the use of arrays. This is the Price List Service we created in Chapter 5. The
service has one public method, called getPriceList,
which expects an array of string SKU values and returns an array of double
price values.
The WSDL file now includes a types
element. Inside this element, we have defined two new complex types. Very
broadly, the XML Schema defines simple types and complex types. Simple types
cannot have element children or attributes, whereas complex types can have
element children and attributes. We have declared complex types in our WSDL
file, because an array may have multiple elements, one for each value in the
array.
The XML Schema requires that any new type you create be based on
some existing data type. This existing base type is specified via the base attribute. You can then choose to modify this base
type using one of two main methods: extension or
restriction. Extension simply means that your new
data type will have all the properties of the base type plus some extra
functionality. Restriction means that your new data type will have all the
properties of the base data type, but may have additional restrictions placed
on the data.
In Example 6-6, we'll create two new complex types via restriction. For example:
<complexType name="ArrayOfString"><complexContent><restriction base="soapenc:Array"><attribute ref="soapenc:arrayType"wsdl:arrayType="string[]"/></restriction></complexContent></complexType>
Example 6-6: PriceListService.wsdl
<?xml version="1.0" encoding="UTF-8"?><definitions name="PriceListService"targetNamespace="http://www.ecerami.com/wsdl/PriceListService.wsdl"xmlns="http://schemas.xmlsoap.org/wsdl/"xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"xmlns:tns="http://www.ecerami.com/wsdl/PriceListService.wsdl"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xsd1="http://www.ecerami.com/schema"><types><schema xmlns="http://www.w3.org/2001/XMLSchema"targetNamespace="http://www.ecerami.com/schema"xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><complexType name="ArrayOfString"><complexContent><restriction base="soapenc:Array"><attribute ref="soapenc:arrayType"wsdl:arrayType="string[]"/></restriction></complexContent></complexType><complexType name="ArrayOfDouble"><complexContent><restriction base="soapenc:Array"><attribute ref="soapenc:arrayType"wsdl:arrayType="double[]"/></restriction></complexContent></complexType></schema></types><message name="PriceListRequest"><part name="sku_list" type="xsd1:ArrayOfString"/></message><message name="PriceListResponse"><part name="price_list" type="xsd1:ArrayOfDouble"/></message><portType name="PriceList_PortType"><operation name="getPriceList"><input message="tns:PriceListRequest"/><output message="tns:PriceListResponse"/></operation></portType><binding name="PriceList_Binding" type="tns:PriceList_PortType"><soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/><operation name="getPriceList"><soap:operation soapAction="urn:examples:pricelistservice"/><input><soap:bodyencodingStyle="http://schemas.xmlsoap.org/soap/encoding/"namespace="urn:examples:pricelistservice"use="encoded"/></input><output><soap:bodyencodingStyle="http://schemas.xmlsoap.org/soap/encoding/"namespace="urn:examples:pricelistservice" use="encoded"/></output></operation></binding><service name="PriceList_Service"><port name="PriceList_Port" binding="tns:PriceList_Binding"><soap:address location="http://localhost:8080/soap/servlet/rpcrouter"/></port></service></definitions>
The WSDL specification requires that arrays be based on the SOAP
1.1 encoding schema. It also requires that arrays use the name ArrayOfXXX, where XXX is the
type of item in the array. The previous example therefore creates a new type
called ArrayOfString. This new type is based on the
SOAP array data type, but it is restricted to holding only string values.
Likewise, the ArrayOfDouble data type creates a new
array type containing only double values.
When using the WSDL types element,
you need to be particularly aware of XML namespace issues. First, note that
the root schema element must include a namespace
declaration for the SOAP encoding specification (http://schemas.xmlsoap.org/soap/encoding/).
This is required because our new data types extend the array definition
specified by SOAP.
Second, the root schema element must
specify a targetNamespace attribute. Any newly
defined elements, such as our new array data types, will belong to the
specified targetNamespace. To reference these data
types later in the document, you must refer back to the same targetNamespace. Hence, our definitions element includes a new namespace declaration:
xmlns:xsd1="http://www.ecerami.com/schema">
xsd1 matches the targetNamespace and therefore enables us to reference the
new data types later in the document. For example, the message element references the xsd1:ArrayOfString data type:
<message name="PriceListRequest"><part name="sku_list" type="xsd1:ArrayOfString"/></message>
TIP: For an excellent and concise overview of W3C Schema complex types and their derivation via extension and restriction, see Donald Smith's article on "Understanding W3C Schema Complex Types." The article is available online at http://www.xml.com/pub/a/2001/08/22/easyschema.html.
Automatically invoking array services
Once you move beyond basic data types, the simple WSDL invocation methods described previously in this chapter no longer work quite as easily. For example, you cannot simply open the GLUE console, pass an array of strings, and hope to receive back an array of doubles. Additional work is necessary, and some manual code is required. Nonetheless, the additional work is minimal, and the discussion that follows focuses on the GLUE platform. We have chosen to focus on the GLUE platform because it represents the most elegant platform for working with complex data types; other tools, such as the IBM Web Services Toolkit, do, however, provide similar functionality.
To get started, you should become familiar with the GLUE wsdl2java command-line tool. The tool takes in a WSDL
file and generates a suite of Java class files to automatically interface with
the specified service. You can then write your own Java class to invoke the
specified service. Best of all, the code you write is minimally simple, and
all SOAP-specific details are completely hidden from your view. (See Figure
6-12.)
|
|
Here is the wsdl2java command-line
usage:
usage: wsdl2java <arguments>where valid arguments are:http://host:port/filename URL of WSDL-c checked exceptions-d directory output directory for files-l user password realm login credentials-m map-file read mapping instructions-p package set default package-v verbose-x command-file command file to execute
Complete information on each argument is available online within the GLUE User Guide at http://www.themindelectric.com/products/glue/releases/GLUE-1.1/docs/guide/index.html. For now, we will focus on the most basic arguments. For example, to generate Java class files for the PriceListService.wsdl file, first make sure that the WSDL file is available publicly on a web site or locally via a web server such as Tomcat. Then, issue the following command:
wsdl2java.bat http://localhost:8080/wsdl/PriceListService.wsdl -p com.ecerami.wsdl.glue
The first argument specifies the location of the WSDL file; the
second argument specifies that the generated files should be placed in the
package com.ecerami.wsdl.glue.
GLUE will automatically download the specified WSDL file and generate two Java class files:
write file IPriceList_Service.javawrite file PriceList_ServiceHelper.java
The first file, IPriceList_Service.java,
is shown in Example
6-7. This file represents a Java interface that mirrors the public methods
exposed by the WSDL file. Specifically, the interface shows a getPriceList( ) method that receives an array of String values, and returns an array of double values.
Example 6-7: IPriceList_Service.java
// generated by GLUEpackage com.ecerami.wsdl.glue;public interface IPriceList_Service{double[] getPriceList( String[] sku_list );}
The second file, PriceList_ServiceHelper.java, is shown in Example
6-8. This is known as a GLUE helper file, and it can dynamically bind to
the service specified by the WSDL file. To access the service, simply call the
static bind( ) method.
Example 6-8: PriceList_ServiceHelper.java
// generated by GLUEpackage com.ecerami.wsdl.glue;import electric.registry.Registry;import electric.registry.RegistryException;public class PriceList_ServiceHelper{public static IPriceList_Service bind( ) throws RegistryException{return bind( "http://localhost:8080/wsdl/PriceListService.wsdl" );}public static IPriceList_Service bind( String url )throws RegistryException{return (IPriceList_Service)Registry.bind( url, IPriceList_Service.class );}}
Once GLUE has generated the interface and helper files, you just
need to write your own class that actually invokes the service. Example
6-9 shows a sample application that invokes the Price List Service. The
code first calls PriceList_ServiceHelper.bind( ),
which then returns an IPriceList_Service object.
All subsequent code behaves as if the Price List Service is a local object,
and all SOAP-specific details are completely hidden from the developer.
Here is a sample output of the Invoke_PriceList application:
Product CatalogSKU: A358185 --> Price: 54.99SKU: A358565 --> Price: 19.99
Example 6-9: Invoke_PriceList.java
package com.ecerami.wsdl;import com.ecerami.wsdl.glue.*;/*** SOAP Invoker. Uses the PriceListServiceHelper to invoke* SOAP service. PriceListServiceHelper and IPriceListService* are automatically generated by GLUE.*/public class Invoke_PriceList {/*** Get Product List via SOAP*/public double[] getPrices (String skus[]) throws Exception {IPriceList_Service priceListService = PriceList_ServiceHelper.bind( );double[] prices = priceListService.getPriceList(skus);return prices;}/*** Main Method*/public static void main (String[] args) throws Exception {Invoke_PriceList invoker = new Invoke_PriceList( );System.out.println ("Product Catalog");String skus[] = {"A358185", "A358565" };double[] prices = invoker.getPrices (skus);for (int i=0; i<prices.length; i++) {System.out.print ("SKU: "+skus[i]);System.out.println (" --> Price: "+prices[i]);}}}
