Thursday, October 13, 2011

WSDL to JAVA and Axis2

Axis2 tools
Axis2 provides an assortment of tools to help developers using the framework. The most important of these are the tools that let you generate the Java linkage code (covered in the last section) from a WSDL service definition and generate a WSDL service definition from existing Java code.
Generate code from WSDL
Axis2 provides a WSDL2Java tool used for code generation from a WSDL service description. You can use this tool directly by running the org.apache.axis2.wsdl.WSDL2Java class as a Java application, or through an Ant task, a Maven plug-in, or Eclipse or IDEA plug-ins. The drawback to having so many choices is that the alternatives generally lag behind the basic Java application in terms of features and bug fixes, so you're usually better off just running the Java application directly (which this article covers).
WSDL2Java offers many different command-line options, with the number of options increasing over time. The Axis2 documentation includes a full reference to the options, so you only look at some of the most important ones here:
  • -o path Sets the target directory for output classes and files (defaults to working directory)
  • -p package-name Sets the target package for generated classes (default is generated from WSDL namespace)
  • -d name Sets the data-binding framework (adb for ADB, xmlbeans for XMLBeans, jibx for JiBX, and none for no data binding; adb is the default)
  • -uw Unwraps doc/lit-wrapped messages, only for supported frameworks (currently ADB and JiBX)
  • -s Generates synchronous client interface only
  • -ss Generates server-side code
  • -sd Generates server-side deployment files
  • -uri path Sets path to WSDL for service to be generated
There are also some WSDL2Java options that are specific to a particular data-binding framework. You can see a couple of these options when you get to the data-binding examples later in this article.
Generate WSDL from code
Axis2 also provides a Java2WSDL tool that you can use to generate a WSDL service definition from existing service code. However, this tool suffers from a number of limitations, including the inability to work with Java collections classes and inflexibility when in comes to structuring the XML generated from Java classes. The limitations are partially due to a lack of interest in this area because of changes in the way web services are developed.
In general, many authorities in the web services and SOA fields frown upon developing web services starting from existing code. They feel that starting from code encourages coupling the XML message structures to a particular implementation, when the whole principle of web services is that the XML should be implementation independent. There's certainly some substance to this concern, but there are also some arguments to the contrary. One is the difficulty involved in writing WSDL service and XML schema definitions from scratch. Both WSDL and schema are complex standards, and the available tools for working with these definitions require a good understanding of the standards to be used effectively. If used by developers without a grounding in the standards, the resulting WSDLs and schemas are often messier than those generated from code. Another issue is strictly practical. Developers often have existing code to implement a functionality that now needs to be exposed as a web service, and they want to be able to use that existing code without substantial changes. So generating WSDL from code is likely to remain an issue for the foreseeable future.
For a more powerful alternative to Java2WSDL you can try the Jibx2Wsdl tool that I developed (see the Resources section for more information). Jibx2Wsdl generates a full set of WSDL-, schema-, and JiBX-binding definitions from one or more supplied service classes. It supports Java 5 enumerations and generic collections, while retaining compatibility with older Java virtual machines (JVMs), and automatically exports Javadocs from Java source files as documentation for the generated WSDL and schema definitions. Jibx2Wsdl also provides an extensive customization mechanism to control how services and XML representations are derived from Java classes, which allows even pre-Java 5 collections to be used with typed data. Although Jibx2Wsdl is specifically intended to make it easy for you to deploy existing classes as web services using the JiBX data-binding framework (also created by me), the generated WSDLs and schemas are data-binding agnostic. You can use them with other Java data-binding frameworks or even other platforms — just generate everything, then throw away the JiBX bindings and keep the rest.
Another alternative, if you're using Java 5 or later, is to use Java Architecture for XML Binding (JAXB) 2.0 and Java API for XML Web Services (JAX-WS) annotations to expose your data objects and service classes as web services. These annotations don't offer the same level of customization as Jibx2Wsdl provides, but they let you embed your configuration information directly in the source code, which some developers find appealing. Axis2 provides experimental support for JAXB 2.0 and JAX-WS as of the 1.2 release, and this will improve in future releases. Future versions of Jibx2Wsdl may also support using JAXB 2.0 and JAX-WS annotations as customizations. (A future article in this series will cover JAXB 2.0 and JAX-WS in more depth and come back to this topic of generating WSDL from code at that time.)
Comparison of data bindings
Axis2 (as of the 1.2 release) fully supports three data-binding alternatives, with more on the way. This article compares sample code using the three fully supported data-binding frameworks and covers some of the strengths and weaknesses of each framework when used with Axis2.
The sample code shown in Listing 4 (which is also included in the sample download in the Download section) is for a library service, which maintains a collection of books organized by subject type. Several operations are defined on the library, including:
  • getBook
  • getTypes
  • addBook
  • getBooksByType
Listing 4 provides a partial view of the WSDL for this service, showing only the portions involved in the getBook operation.

Listing 4. Library service WSDL
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
    xmlns:wns="http://ws.sosnoski.com/library/wsdl"
    xmlns:tns="http://ws.sosnoski.com/library/types"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
  <wsdl:types>
  
    <schema elementFormDefault="qualified"
        targetNamespace="http://ws.sosnoski.com/library/wsdl"
        xmlns="http://www.w3.org/2001/XMLSchema">
      
      <import namespace="http://ws.sosnoski.com/library/types"/>
        
      <element name="getBook">
        <complexType>
          <sequence>
            <element name="isbn" type="string"/>
          </sequence>
        </complexType>
      </element>
      
      <element name="getBookResponse">
        <complexType>
          <sequence>
            <element name="getBookReturn" minOccurs="0" type="tns:BookInformation"/>
          </sequence>
        </complexType>
      </element>
      ...
      
    </schema>
    
    <schema elementFormDefault="qualified"
        targetNamespace="http://ws.sosnoski.com/library/types"
        xmlns="http://www.w3.org/2001/XMLSchema">
      
      <complexType name="BookInformation">
        <sequence>
          <element name="author" minOccurs="0" maxOccurs="unbounded" type="string"/>
          <element name="title" type="string"/>
        </sequence>
        <attribute name="type" use="required" type="string"/>
        <attribute name="isbn" use="required" type="string"/>
      </complexType>
      ...
      
    </schema>

  </wsdl:types>

  <wsdl:message name="getBookRequest">
    <wsdl:part element="wns:getBook" name="parameters"/>
  </wsdl:message>

  <wsdl:message name="getBookResponse">
    <wsdl:part element="wns:getBookResponse" name="parameters"/>
  </wsdl:message>
  ...
  
  <wsdl:portType name="Library">

    <wsdl:operation name="getBook">
      <wsdl:input message="wns:getBookRequest" name="getBookRequest"/>
      <wsdl:output message="wns:getBookResponse" name="getBookResponse"/>
    </wsdl:operation>
    ...

  </wsdl:portType>

  <wsdl:binding name="LibrarySoapBinding" type="wns:Library">

    <wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

    <wsdl:operation name="getBook">
    
      <wsdlsoap:operation soapAction="urn:getBook"/>
      
      <wsdl:input name="getBookRequest">
        <wsdlsoap:body use="literal"/>
      </wsdl:input>
      
      <wsdl:output name="getBookResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
      
    </wsdl:operation>
    ...

  </wsdl:binding>
  ...

</wsdl:definitions>

The actual service implementation code is simple, populating a library instance with a hardcoded list of books. The client code executes a sequence of queries in the following order:
  1. A getBook
  2. A getTypes
  3. A pair of addBooks, with the second one returning a SOAP Fault for attempting to add a duplicate book ID
  4. A getBooksByType

The implementation details differ between the examples, because each uses the data objects appropriate to that data binding. Unless otherwise stated, all code shown is consistent with both Axis2 1.1.1 and 1.2. The Axis2 1.3 release, in progress as this article goes to publication, requires some minor changes to the code due to a change in the naming of generated exception classes corresponding to service faults. Both versions of the code are supplied for download (see the Download section).
In this article, you only look at the client code, though the supplied download (see the Download section) includes both client and server code along with Ant build files for all the examples. Next, review the client code for the three data-binding frameworks, and learn the strengths and weaknesses of each approach.
Axis2 Data Binding
ADB is a data-binding extension for Axis2. Unlike the other data-binding frameworks, ADB code is usable only for Axis2 web services. This restriction is a significant limitation of ADB, but also confers some advantages. Because ADB is integrated with Axis2, the code can be optimized for Axis2 requirements. One example of this is that ADB builds on the AXis Object Model (AXIOM) document model at the core of Axis2 (as discussed in the previous article in this series). ADB also offers some enhanced features not currently available with the other data-binding frameworks, including automatic attachment handling. WSDL2Java provides full support for ADB code generation, including generating data model classes corresponding to the XML schema components.
ADB schema support has some limitations. In the current Axis2 1.2 release, these limitations include schema features, such as compositors with maxOccurs="unbounded", schema definitions with attributeFormDefault="qualified", and some similar variations. But the Axis2 1.2 ADB schema support is much better than that in the Axis2 1.1 release, and support is likely to continue improving with each release of the Axis2 framework until all major schema features are supported.
The basic form of ADB code generation uses a direct model where separate classes correspond to the input and output messages used for each operation. Listing 5 shows the most interesting parts of the client code for the example application using this basic form of ADB code generation. The client code demonstrates the interactions with the ADB-generated classes, such as the GetBookDocument and GetBookDocument.GetBook classes used for the parameter to the getBook() method call, and the GetBookResponseDocument and BookInformation classes used for the return from this call.

Listing 5. ADB client code
// create the client stub
AdbLibraryStub stub = new AdbLibraryStub(target);
        
// retrieve a book directly
String isbn = "0061020052";
GetBook gb = new GetBook();
gb.setIsbn(isbn);
GetBookResponse gbr = stub.getBook(gb);
BookInformation book = gbr.getGetBookReturn();
if (book == null) {
  System.out.println("No book found with ISBN '" + isbn + '\'');
} else {
  System.out.println("Retrieved '" + book.getTitle() + '\'');
}
        
// retrieve the list of types defined
GetTypesResponse gtr = stub.getTypes(new GetTypes());
TypeInformation[] types = gtr.getGetTypesReturn();
System.out.println("Retrieved " + types.length + " types:");
for (int i = 0; i < types.length; i++) {
  System.out.println(" '" + types[i].getName() + "' with " +
    types[i].getCount() + " books");
}
        
// add a new book
String title = "The Dragon Never Sleeps";
isbn = "0445203498";
try {
  AddBook ab = new AddBook();
  ab.setType("scifi");
  ab.setAuthor(new String[] { "Cook, Glen" });
  ab.setIsbn(isbn);
  ab.setTitle(title);
  stub.addBook(ab);
  System.out.println("Added '" + title + '\'');
  ab.setTitle("This Should Not Work");
  stub.addBook(ab);
  System.out.println("Added duplicate book - should not happen!");
} catch (AddDuplicateFaultException e) {
  System.out.println("Failed adding '" + title +
    "' with ISBN '" + isbn + "' - matches existing title '" +
    e.getFaultMessage().getBook().getTitle() + '\'');
}
        
// create a callback instance
CallbackHandler cb = new CallbackHandler();
        
// retrieve all books of a type asynchronously
GetBooksByType gbbt = new GetBooksByType();
gbbt.setType("scifi");
stub.startgetBooksByType(gbbt, cb);
long start = System.currentTimeMillis();
synchronized (cb) {
  while (!cb.m_done) {
    try {
      cb.wait(100);
    } catch (Exception e) {}
  }
}
System.out.println("Asynchronous operation took " +
  (System.currentTimeMillis()-start) + " millis");
if (cb.m_response != null) {
  BookInformation[] books = cb.m_response.getGetBooksByTypeReturn();
  ...

Listing 5 uses code generated with the -u WSDL2Java option, which is specific to ADB. With this option, a separate Java source file is generated for each message and data model class; if you don't use this option, ADB code generation generates all these classes as static inner classes of the generated stub. It's considerably easier to work with the separate classes, so the -u option should be a given if you use ADB.
The direct form of code generation leads to a large number of separate classes for the input and output of each operation (irrespective of the -u option, which just organizes the same classes differently). These generated message classes often contain little (or even no, in the case of the GetTypes class) useful data but are required by the generated method signatures. Fortunately there's an alternative form of code generation that applies to many common services.

No comments:

Post a Comment

Blog Archive