by Jeff Hanson
he onslaught of Web services messaging and XML-based data transfer has led to a need to express these message exchanges in some structured way. The Web services Description Language (WSDL) deals with this need by defining an XML grammar for exposing services as sets of endpoints for exchanging related messages. WSDL (pronounced "wizdle") documents provide a standard means for describing services by outlining the data types consumed by the services, the URL for communicating with the services, and the names by which services are known.WSDL documents are quickly becoming the key to successful B2B interoperability. A WSDL document provides a generic Web service description without regard for the specific programming language in which the services are written. Developers can use WSDL documents to generate client code that can communicate with the corresponding Web services. This generic form of service/component description has been attempted in the past by various binary forms of interface description languages (IDLs), but never has it seen such widespread use as with WSDL and Web services.
One reason for the success of WSDL-based service description is the pervasive use of XML and its ability to define virtually any data type for any programming language in a generic way. This article discusses how to handle custom WSDL data types in the Java programming language.
Introducing WSDL
WSDL documents are XML documents that describe services as a set of message-enabled or procedure-oriented abstract endpoints. These operations and/or messages and their associated data types are described conceptually, and then bound concretely to a network protocol, message format, and programming language as needed.
A WSDL document defines the following elements for describing services:
- Types—data type definitions
- Message—an abstract definition of the data being transferred
- Operation—an abstract description of a service procedure
- Port Type—an abstract set of operations supported by one or more endpoints
- Binding—a concrete protocol and data format for a given port type
- Port—a single endpoint defined as a binding and a network address
- Service—a collection of related endpoints or ports
It's challenging for Web services providers to expose a data type system that can be understood by the broadest range of Web services consumers, yet that understanding must be in place for Web service consumers and providers to communicate effectively. Because different programming languages each have their own set of idiosyncrasies, creating a common data type system is of primary importance.
In contrast to previous attempts at a generic interface definition language (IDL) and a common data-type system, such as COM, DCOM, and CORBA, WSDL solves the common data-type problem by aiming for maximum flexibility rather than attempting to create an all-inclusive data-typing standard. Thus, WSDL is not bound to any one data type system. However, WSDL does recommend the W3C XML Schema specification as the canonical type system, thereby intrinsically treating the most widely used data type system as the default.
WSDL and the XML Schema Specification
Even though you can use any encoding format for WSDL type definitions, the WSDL specification suggests using XML Schema Definitions (XSD). XSD or XML Schema is a W3C standard that seeks to supersede the document type definition (DTD) standard for defining structure within an XML document. XML Schema defines a comprehensive set of programming-language independent, primitive/simple data types, such as doubles, strings, and integers, as well as a mechanism for creating complex types using the predefined simple types (see Table 1).
Table 1. XML Schema Simple Types: The table shows a list of the simple types predefined in XSD.
Simple Type | Examples |
string | This is a string |
normalizedString | This is a normalized string |
token | This is a token |
base64Binary | GpM7 |
hexBinary | 0FB7 |
integer | -1, 0, 103 |
positiveInteger | 1, 2, 215 |
negativeInteger | -563, -2, -1 |
nonNegativeInteger | 0, 1, 2, 672 |
nonPositiveInteger | 37 -2, -1, 0 |
long | -9223372036854775808, ... -1, 0, 1, ... 9223372036854775807 |
unsignedLong | 0, 1, ... 18446744073709551615 |
int | -2147483648, ... -1, 0, 1, ... 2147483647 |
unsignedInt | 0, 1, ...4294967295 |
short | -32768, ... -1, 0, 1, ... 32767 |
unsignedShort | 0, 1, ... 65535 |
byte | -128, ...-1, 0, 1, ... 127 |
unsignedByte | 0, 1, ... 255 |
decimal | -1.23, 0, 123.4, 1000.00 |
float | -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN |
double | -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN |
boolean | true, false, 1, 0 |
duration | P1Y2M3DT10H30M12.3S |
dateTime | 1999-05-31T13:20:00.000-05:00 |
date | 1999-05-31 |
time | 13:20:00.000, 13:20:00.000-05:00 |
gYear | 1999 |
gYearMonth | 1999-02 |
gMonth | —05 |
gMonthDay | —05-31 |
gDay | —-31 |
Name | shipTo |
QName | po:USAddress |
NCName | USAddress |
anyURI | http://www.example.com/,http://www.example.com/doc.html#ID5 |
language | en-GB, en-US, fr |
The WSDL <types> element defines data types that are to be exchanged between service consumers and providers. If the service defined by the WSDL document uses only the built-in XML Schema simple types, you don't have to include a <types> element; instead, you can use the element to define your own types. Listing 1 shows a simple WSDL document that includes a <types> element:
Declaring New Data Types in a WSDL Document
The XML Schema specification provides a feature for defining custom data types. Whenever a service requires data that can be represented more conveniently with abstractions beyond the scope of the simple XML Schema data types, these abstractions can be declared as new data types within the WSDL <types> element. The example in Listing 1 defined the custom data type shown below:
<complexType name="HelloWorld">
<sequence>
<element name="message"
type="string"
minOccurs="1"/>
</sequence>
</complexType>
The definition represents the message data type as a simple string, and the new type definition adds a constraint to the data type—the element is required to appear because the value of the minOccurs attribute is greater than zero.A more sophisticated use of a custom data type might include contact information for a user such as the following:
<complexType name="AddressType">
<sequence>
<element name="streetName"
type="string"
minOccurs="1"/>
<element name="city"
type="string"
minOccurs="1"/>
<element name="state"
type="string"
minOccurs="1"/>
<element name="zip"
type="string"
minOccurs="1"/>
<element name="phoneNumber"
type="string"
minOccurs="1"/>
</sequence>
</complexType>
In the preceding example, <AddressType> defines a custom data type that encapsulates user contact information. Notice that the <state> element is defined as a simple string, allowing virtually any text content of any length to occur in the element. A more robust method would constrain the state element to a two-letter abbreviation by defining a custom "two-letter-state" data type and then referencing that in the <AddressType> data type. Here's a custom string data type restricted to a length of two: <simpleType name="two-letter-state">
<restriction base="string">
<length value="2"/>
</restriction>
</simpleType>
It's relatively easy to see the relationship between data type definitions in XSD schema and object definitions in Java (or other languages) expressed in code. You can make these relationships explicit using mapping software.XSD Data Types and Java Data Types
The Java programming language provides data types that can be mapped to represent most of the standard XSD data types. Table 2 contains a list of mappings from XSD data types to Java data types:
Table 2. XSD-to-Java Mappings: The table shows how you can map a list of XSD data types to their equivalents in Java.
XSD Type | Java Type |
base64Binary | byte[] |
hexBinary | byte[] |
boolean | Boolean |
byte | Byte |
dateTime | java.util.Calendar |
date | java.util.Calendar |
time | java.util.Calendar |
decimal | java.math.BigDecimal |
double | Double |
float | Float |
hexBinary | byte[] |
int | Int |
unsignedShort | Int |
integer | java.math.BigInteger |
long | Long |
unsignedInt | Long |
QName | javax.xml.namespace.QName |
short | Short |
unsignedByte | Short |
string | java.lang.String |
anySimpleType | java.lang.String |
The mappings in Table 2 illustrate a comprehensive list of simple data type mappings; however, as you need more complex abstractions and need to extend XSD types with custom type declarations, it also becomes more complex to map such types to Java. Mapping custom type declarations is the focus of this article. First, though, here's a brief overview of the XML tools you'll need to implement the mapping operation.
Streaming API for XML (StAX)
XSD and WSDL are XML-based grammars that you can process using standard XML-processing techniques. XML processing in Java has evolved into two main approaches:
- The Simple API for XML processing (SAX)
- The Document Object Model (DOM)
The Streaming API for XML (StAX) specification defines a new and highly-efficient API for parsing and streaming XML. The StAX specification defines a bi-directional API for reading and writing XML. StAX allows developers to maintain parsing control by exposing a simple iterator-based API, with intuitive methods such as next() and hasNext(), as well as an underlying stream of events. This iterator-based API lets you "pull" the next event rather than handling the event in a callback. The cursor API facilitates processing-control even further by letting you stop processing, skip ahead to sections of a document, and retrieve subsections of a document.
Introducing XMLBeans
Apache XMLBeans is an open source, XML-Java binding tool based on the StAX specification. You can use XMLBeans to generate Java classes and interfaces from an XML schema, subsequently using those generated Java classes to parse or generate XML documents that conform to the schema.
BEA originated XMLBeans and then donated it to the Apache Software Foundation. XMLBeans provides three major APIs for access to XML and schema:
- Strongly-typed access—XMLBeans can compile an XML schema to generate Java classes and interfaces. The generated classes and interfaces all extend a common base class and provide strongly-typed getters and setters for each of the elements found in the schema.
- Cursor-style access—From any generated class you can get instance of a positional cursor object that allows low-level access to the XML information.
- Schema-style access—XMLBeans provides a schema object model that you can use to reflect on the XML schema.
To install XMLBeans, download the binary distribution of XMLBeans, and extract the archive into the directory of your choice. Next, define an XMLBEANS_HOME environment variable with a value set to the directory where you extracted the binary distribution files. Finally, add the .jar and .zip files contained in the XMLBeans distribution to your project.
The remainder of this article demonstrates how to implement a simple WSDL-to-Java interpreter using the XMLBeans framework and APIs. It's important to understand that as Web service providers increasingly adopt WSDL as the doctrine of exchange for integrating with their systems via publicly-accessible services, service clients must have the ability to process WSDL dynamically to leverage those services. That's because the WSDL document may often be the only point of access to service information. Fortunately, when properly prepared, the WSDL document is also all that clients need.
The XMLBeans framework facilitates the ability to compile a WSDL file into Java objects. With this facility, developers can dynamically generate Web service clients from WSDL documents, using the resulting Java classes and interfaces associated with the data types and methods of the Web service. Interpreting WSDL dynamically offers a degree of power greater than standard static XML-parsing and processing techniques.
Building Custom WSDL Interpreters with XMLBeans
The WSDL2Java class below is an example of a simple WSDL-to-Java interpreter using XMLBeans. You can download all the source code for this article to follow along.
package com.jeffhanson.clienttier;
import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.xb.xsdschema.*;
import org.apache.xmlbeans.impl.xb.xsdschema.impl.*;
import org.apache.xmlbeans.impl.xb.substwsdl.*;
import org.apache.xmlbeans.impl.util.FilerImpl;
import
org.apache.xmlbeans.impl.schema.SchemaTypeSystemCompiler;
import
org.apache.xmlbeans.impl.schema.SchemaTypeLoaderImpl;
import org.apache.xmlbeans.impl.schema.StscState;
import org.apache.xmlbeans.impl.common.XmlErrorWatcher;
import org.apache.xmlbeans.impl.common.IOUtil;
import java.util.*;
import java.io.File;
import repackage.Repackager;
public class WSDL2Java
{
private static class MyResolver
implements org.xml.sax.EntityResolver
{
public
org.xml.sax.InputSource resolveEntity(
String publicId,
String systemId)
{
System.out.println("System ID: " + systemId);
return null;
}
}
...
The generate method shown in Listing 2 parses a given WSDL file and generates Java code representing the data types found in the <Types> section of the WSDL document.The compileSchemas method creates the XMLBeans-specific schema files (.xsb's) and saves them to disk.
private SchemaTypeSystem compileSchemas(
String schemaFilesDir, ArrayList scontentlist,
MyResolver entityResolver,
XmlErrorWatcher errorListener)
{
SchemaDocument.Schema[] sdocs =
(SchemaDocument.Schema[])scontentlist.toArray(
new SchemaDocument.Schema[scontentlist.size()]);
ResourceLoader cpResourceLoader = null;
SchemaTypeLoader linkTo =
SchemaTypeLoaderImpl.build(null,
cpResourceLoader, null);
File baseDir = new File(System.getProperty(
"user.dir"));
java.net.URI baseURI = baseDir.toURI();
XmlOptions opts = new XmlOptions();
opts.setCompileNoValidation();
opts.setEntityResolver(entityResolver);
Map sourcesToCopyMap = new HashMap();
File schemasDir = IOUtil.createDir(new File("."),
schemaFilesDir);
// create parameters for the main compile function
SchemaTypeSystemCompiler.Parameters params =
new SchemaTypeSystemCompiler.Parameters();
params.setName(null);
params.setSchemas(sdocs);
params.setLinkTo(linkTo);
params.setOptions(opts);
params.setErrorListener(errorListener);
params.setJavaize(true);
params.setBaseURI(baseURI);
params.setSourcesToCopyMap(sourcesToCopyMap);
params.setSchemasDir(schemasDir);
System.out.println("Compiling schemas...");
try
{
// create schema files (.xsb's)
SchemaTypeSystem sts =
SchemaTypeSystemCompiler.compile(params);
// now save .xsb's to disk
sts.saveToDirectory(schemasDir);
System.out.println(
"Schema compilation succeeded");
return sts;
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
The generateJavaSource method generates Java source files for the WSDL data types that have previously been added to a given type system. private List generateJavaSource(String schemaClassesDir,
String schemaSrcDir, SchemaTypeSystem sts)
{
File classesDir = new File(schemaClassesDir);
File srcDir = IOUtil.createDir(new File("."),
schemaSrcDir);
// now, generate the source files
XmlOptions options = new XmlOptions();
boolean verbose = false;
boolean incrSrcGen = false;
Repackager repackager = null;
FilerImpl filer = new FilerImpl(classesDir,
srcDir, repackager, verbose, incrSrcGen);
System.out.println("Generating Java source...");
if (SchemaTypeSystemCompiler.generateTypes(sts,
filer, options))
{
System.out.println(
"Java source generation succeeded");
return filer.getSourceFiles();
}
else
{
System.out.println("Java source generation failed");
}
return null;
}
The addWsdlSchemas method shown in Listing 3 extracts the <Types> array from a WSDL definition document and adds the schemas representing each type to the content list.The JavaSourceCompiler class uses utilities from the XMLBeans API to compile previously generated Java source files:
package com.jeffhanson.clienttier;
import org.apache.xmlbeans.impl.tool.CodeGenUtil;
import org.apache.xmlbeans.impl.common.IOUtil;
import java.io.File;
import java.util.List;
public class JavaSourceCompiler
{
public JavaSourceCompiler()
{
}
public void generate(String schemasDirName,
List sourceFiles,
String classesDirName)
{
File classesDir = new File(classesDirName);
File schemasDir = IOUtil.createDir(new File("."),
schemasDirName);
boolean verbose = false;
// now, compile source files
String compiler =
"/tools/VMs/j2sdk1.4.2_04/bin/javac.exe";
File[] tempClasspath = CodeGenUtil.systemClasspath();
File[] classpath = new
File[tempClasspath.length + 1];
System.arraycopy(tempClasspath, 0, classpath, 0,
tempClasspath.length);
classpath[tempClasspath.length] = schemasDir;
boolean debug = false;
String javasource = null;
String memoryInitialSize =
CodeGenUtil.DEFAULT_MEM_START;
String memoryMaximumSize =
CodeGenUtil.DEFAULT_MEM_MAX;
boolean quiet = true;
System.out.println("Compiling Java source files...");
if (CodeGenUtil.externalCompile(
sourceFiles, classesDir,
classpath, debug, compiler, javasource,
memoryInitialSize, memoryMaximumSize,
quiet, verbose))
{
System.out.println("Source compile succeeded");
}
else
{
System.out.println("Source compile failed");
}
}
}
I urge you to download the source code for this article and experiment with it yourself. Web services messaging and XML-based data transfer has created a need to for a structured way to express message exchanges. The Web services Description Language (WSDL) deals with this need by defining an XML-based service description and generic data-type system for any programming language.The WSDL data-type system defines data types that can be exchanged between service consumers and providers. WSDL allows you to use the built-in XML-Schema simple types, or you can define your own custom types.
The Streaming API for XML (StAX) specification defines a new API using a bi-directional API for parsing and streaming XML. StAX allows you to maintain parsing control by exposing a simple iterator-based API, as well as a cursor API. The iterator-based API lets you "pull" events as needed. The cursor API facilitates processing-control even further by letting you stop processing, skip ahead to sections of a document, and retrieve subsections of a document.
Apache XMLBeans (originated by BEA and then donated to Apache) is an open source, XML-Java binding tool based on the StAX specification that can be used to generate Java classes and interfaces from an XML schema. These classes and interfaces can then be used to parse or generate XML documents that conform to the schema.
The combination of StAX and the XMLBeans framework allows developers to dynamically interpret and operate on WSDL documents and the data-types declared therein with a greater degree of power and flexibility than standard static XML-parsing and processing techniques.
http://www.devx.com/Java/Article/29286/1954