Using Java To Handle Custom WSDL Data Types
Using Java To Handle Custom WSDL Data Types
transfer has led to the need to express the 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 means for describing Web services without regards for the specific programming language in which the services are written. Developers can use WSDL documents to generate client code with which to communicate the corresponding Web services. This generic form of service/component description has been attempted in the past by various forms of interface description languages (IDLs), but never has it seen such widespread use as with WSDL and Web services. One of the reasons 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 is embodied within an XML document and describes services as a set of messageenabled 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
One of the greatest challenges for Web services providers is the task of exposing a data type system that is understood by the broadest range of Web services consumers. This understanding must be in place in order for a Web service consumer and provider to communicate effectively. Since each programming language has its own set of idiosyncratic differences when contrasted with another programming language, creating a common data type system is of primary importance. WSDL solves this problem by aiming for maximum flexibility rather than aiming 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 treating the most widely used data type system intrinsically as the default.
WSDL and the XML Schema Specification
Even though any encoding format can be used 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 programminglanguage 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. This list is shown in table 1: Simple Type string normalizedString token base64Binary hexBinary integer positiveInteger negativeInteger nonNegativeInteger nonPositiveInteger long unsignedLong int unsignedInt short unsignedShort byte unsignedByte decimal float double boolean duration dateTime date time gYear gYearMonth gMonth gMonthDay gDay Name Examples This is a string This is a normalized string This is a token GpM7 0FB7 -1, 0, 103 1, 2, 215 -563, -2, -1 0, 1, 2, 672 37 -2, -1, 0 -9223372036854775808, ... -1, 0, 1, ... 9223372036854775807 0, 1, ... 18446744073709551615 -2147483648, ... -1, 0, 1, ... 2147483647 0, 1, ...4294967295 -32768, ... -1, 0, 1, ... 32767 0, 1, ... 65535 -128, ...-1, 0, 1, ... 127 0, 1, ... 255 -1.23, 0, 123.4, 1000.00 -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN true, false, 1, 0 P1Y2M3DT10H30M12.3S 1999-05-31T13:20:00.000-05:00 1999-05-31 13:20:00.000, 13:20:00.000-05:00 1999 1999-02 --05 --05-31 ---31 shipTo
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, the types element is not required. The following illustrates a simple WSDL document with a types element defined: <?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <wsdl:types> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:helloworld"> <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/> <complexType name="HelloWorld"> <sequence> <element name="message" type="string" minOccurs="1"/> </sequence> </complexType> </schema> </wsdl:types> <wsdl:message name="HelloWorldResponse"> <wsdl:part name="return" type="HelloWorld"/> </wsdl:message> <wsdl:message name="HelloWorldRequest"> </wsdl:message> <wsdl:portType name="HelloWorldService"> <wsdl:operation name="HelloWorld">
<wsdl:input name="HelloWorldRequest" message="intf:HelloWorldRequest"/> <wsdl:output name="HelloWorldResponse" message="intf:HelloWorldResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="AxisServletSoapBinding" type="intf:HelloWorldService"> <wsdlsoap:binding style="rpc" transport= "http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="HelloWorld"> <wsdlsoap:operation soapAction=""/> <wsdl:input name="HelloWorldRequest"> <wsdlsoap:body use="encoded" encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:helloworld"/> </wsdl:input> <wsdl:output name="HelloWorldResponse"> <wsdlsoap:body use="encoded" encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:helloworld"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="HelloWorldService"> <wsdl:port name="HelloWorldServlet" binding="intf:AxisServletSoapBinding"> <wsdlsoap:address location="http://127.0.0.1/services/HelloWorld"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
In the next sections, I discuss using XML Schema to create new data types and consuming them in the Java programming language. 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. For example, in the previous example, one custom data type was defined: <complexType name="HelloWorld"> <sequence> <element name="message" type="string" minOccurs="1"/> </sequence> </complexType> Even though the message data type is represented as a simple string, the new type definition adds a constraint to the data type in that the element is required to appear since 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>
AddressType in the preceding example defines a custom data type that can be used to encapsulate contact information for a user. Notice that the state element is defined as a simple string, allowing virtually any textual content of any length to occur in the element. To constrain the state element to a two-letter abbreviation, a custom two-letter-state data type can be defined and used by the AddressType data type. The following illustrates a custom string data type that is restricted to a length of two: <simpleType name="two-letter-state"> <restriction base="string"> <length value="2"/> </restriction> </simpleType> 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: XSD Type base64Binary hexBinary boolean byte dateTime date time decimal double float hexBinary int unsignedShort integer long unsignedInt QName short unsignedByte string anySimpleType Table 2 Java Type byte[] byte[] boolean byte java.util.Calendar java.util.Calendar java.util.Calendar java.math.BigDecimal double float byte[] int int java.math.BigInteger long long javax.xml.namespace.QName short short java.lang.String java.lang.String
The mappings in table 2 illustrate a comprehensive list of data types for simple data representations. However when more complex abstractions are required and the XSD types are extended with custom type declarations, Java developers must be equipped to handle the new types. This is the focus of the following sections.
Introducing the Streaming API for XML (StAX) XSD and WSDL are XML-based grammars that can be processed using standard XMLprocessing techniques. XML processing has fallen into two main approaches: 1. the Simple API for XML processing (SAX) 2. the Document Object Model (DOM) SAX exposes a callback-styled parsing API while DOM provides a randomly-accessible tree-like structure. The Streaming API for XML (StAX) specification found at http://jcp.org/jsr/detail/173.jsp defines a new highly-efficient API for parsing and streaming XML. The StAX specification defines a bi-directional API for reading and writing XML. StAX allows a developer to maintain parsing control by exposing a simple iterator-based API, with familiar methods such as next() and hasNext(), and an underlying stream of events. The iterator-based API allows a developer to pull the next event rather than handling the event in a callback. The cursor API facilitates processing-control even further by allowing a developer to 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. XMLBeans can be used to generate Java classes and interfaces from an XML Schema. The generated Java classes may be used 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 An XML schema can be compiled 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 which allows low-level access to the XML information. Schema-style access XMLBeans provides a schema object model that can be used to reflect on the XML schema.
Installing XMLBeans
To install XMLBeans, download the binary distribution of XMLBeans found at http://xmlbeans.apache.org/, 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. Then add the .jar and .zip files contained in the XMLBeans distribution to your project. This will allow you to begin using the XMLBeans API. For purposes of this article, I will demonstrate how to implement a simple WSDL-to-Java interpreter using the XMLBeans framework and APIs.
Web service providers are beginning to adopt WSDL as the doctrine of exchange for integrating with their systems via publicly-accessible services. Accessing WSDL documents that describe services and enabling service clients with the ability to process them in a dynamic manner, allows service clients to leverage these services, having access only to the associated WSDL. The XMLBeans framework facilitates the ability to compile a WSDL file into Java objects. With this facility, Web service clients can be dynamically generated from a WSDL document, and Java classes and interfaces associated with the data types and methods of the Web service can be used by the Web service clients. This ability to interpret a WSDL document dynamically offers a greater degree of power over standard static XML-parsing and processing techniques. The WSDL2Java class illustrated below demonstrates a simple WSDL-to-Java interpreter using XMLBeans: package com.jeffhanson.clienttier; import import import import import import import import import import org.apache.xmlbeans.*; org.apache.xmlbeans.impl.xb.xsdschema.*; org.apache.xmlbeans.impl.xb.xsdschema.impl.*; org.apache.xmlbeans.impl.xb.substwsdl.*; org.apache.xmlbeans.impl.util.FilerImpl; org.apache.xmlbeans.impl.schema.SchemaTypeSystemCompiler; org.apache.xmlbeans.impl.schema.SchemaTypeLoaderImpl; org.apache.xmlbeans.impl.schema.StscState; org.apache.xmlbeans.impl.common.XmlErrorWatcher; 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 will parse a given WSDL file and generate Java code representing the data types found in the Types section of the WSDL document. public List generate(String schemaFilesDir, String schemaClassesDir, String schemaSrcDir, String wsdlFileName) { boolean doNotValidateContents = false; XmlObject wsdlDoc = null; MyResolver entityResolver = new MyResolver(); ArrayList outerErrorListener = new ArrayList(); XmlErrorWatcher errorListener = new XmlErrorWatcher(outerErrorListener); ArrayList scontentlist = new ArrayList(); try { SchemaTypeLoader loader = XmlBeans.typeLoaderForClassLoader( SchemaDocument.class.getClassLoader()); XmlOptions options = new XmlOptions(); options.setLoadLineNumbers(); options.setLoadSubstituteNamespaces( Collections.singletonMap( "http://schemas.xmlsoap.org/wsdl/", "http://www.apache.org/internal/xmlbeans/wsdlsubst")); options.setEntityResolver(entityResolver); System.out.println("Parsing WSDL: " + wsdlFileName + "..."); wsdlDoc = loader.parse(new File(wsdlFileName), null, options); if (!(wsdlDoc instanceof DefinitionsDocument)) { // Create new definitions doc DefinitionsDocument.Definitions definitions = DefinitionsDocument. Definitions.Factory.newInstance(); // Set types array
definitions.setTypesArray( wsdlDoc.selectPath("wsdl:types")); DefinitionsDocument defDoc = DefinitionsDocument.Factory.newInstance(); defDoc.setDefinitions(definitions); // Adding constructed schema addWsdlSchemas(wsdlFileName, defDoc, errorListener, doNotValidateContents, scontentlist); } else { addWsdlSchemas(wsdlFileName, (DefinitionsDocument)wsdlDoc, errorListener, doNotValidateContents, scontentlist); } } catch (XmlException e) { errorListener.add(e.getError()); } catch (Exception e) { StscState.addError(errorListener, XmlErrorCodes.CANNOT_LOAD_FILE, new Object[] { "wsdl", wsdlFileName, e.getMessage() }, wsdlDoc); } SchemaTypeSystem sts = compileSchemas(schemaFilesDir, scontentlist, entityResolver, errorListener); return generateJavaSource(schemaClassesDir, schemaSrcDir, sts); }
The compileSchemas method will create the XMLBeans-specific schema files (.xsbs) and save 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 will generate Java source files for the WSDL data types that have been previously 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 will extract the Types array from a WSDL definition document and add the schemas representing each type to the content list. private void addWsdlSchemas(String wsdlFileName, DefinitionsDocument definitionsDocument, XmlErrorWatcher errorListener, boolean doNotValidateContents, ArrayList scontentlist) { XmlOptions opts = new XmlOptions().setErrorListener(errorListener); if (doNotValidateContents) { opts.setValidateTreatLaxAsSkip(); } XmlObject[] types = definitionsDocument.getDefinitions().getTypesArray(); for (int j = 0; j < types.length; j++) { XmlObject[] schemas = types[j].selectPath("declare namespace" + " xs=\"http://www.w3.org/2001/XMLSchema\"" + " xs:schema"); if (schemas.length == 0) { StscState.addWarning(errorListener, "The WSDL " + wsdlFileName + " has no schema documents in namespace " + "'http://www.w3.org/2001/XMLSchema'", XmlErrorCodes.GENERIC_ERROR, definitionsDocument); continue; } for (int k = 0; k < schemas.length; k++) {
if (schemas[k] instanceof SchemaDocument.Schema) { SchemaDocumentImpl.SchemaImpl schemaImpl = (SchemaDocumentImpl.SchemaImpl)schemas[k]; System.out.println("Validating schema..."); if (schemaImpl.validate(opts)) { System.out.println("Schema passed validation"); scontentlist.add(schemas[k]); } else { System.out.println("Schema failed validation"); scontentlist.add(schemas[k]); } } } } } } 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"); } } } Conclusion The pervasive use of Web services messaging and XML-based data transfer has led to the need to express the message exchanges in some structured way. WSDL deals with this need by defining an XML-based grammar for exposing services as sets of endpoints for exchanging related messages. WSDL documents are quickly becoming the key to successful B2B interoperability. A WSDL document provides a generic means for describing Web services without regards for the specific
programming language in which the services are written. This generic form of service description is not new, but it has never seen such widespread use as with WSDL and Web services. One of the reasons 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 discussed how to handle custom WSDL data types in the Java programming language.