Java Web Services: Axis2 Data Binding

Axis2 supports a variety of data-binding frameworks for easy access to XML data

The Apache Axis2 Web services framework was designed from the start to support multiple XML data-binding approaches. The current release provides full support for XMLBeans and JiBX data binding, as well as the custom Axis Data Binding (ADB) approach developed specifically for Axis2. This article shows you how to use these different data bindings with Axis2 and explains why you might prefer one over the others for your application.

Share:

Dennis Sosnoski (dms@sosnoski.com), Consultant, Sosnoski Software Solutions, Inc.

Dennis Sosnoski photoDennis Sosnoski is a consultant specializing in Java-based SOA and web services. His professional software development experience spans over 30 years, with the last seven years focused on server-side XML and Java technologies. Dennis is the lead developer of the open source JiBX XML Data Binding framework built around Java classworking technology and the associated JiBXSoap web services framework, as well as a committer on the Apache Axis2 web services framework. He was also one of the expert group members for the JAX-WS 2.0 and JAXB 2.0 specifications.



26 July 2007

Also available in Portuguese

Even though XML message exchange is at the core of Web services, most Web service applications aren't concerned with XML. Instead, these applications want to exchange business data that's specific to the application. XML is, in this case, just a format used to represent the business data to support a Web service interface. XML works well for this purpose, because it provides a platform-independent representation that can be handled by a variety of tools. But applications ultimately need to convert the XML to or from their own internal data structures to use the data within the application.

Data binding is the term used for techniques that handle this conversion between XML and application data structures. It's possible to write custom data-binding code for an application, but most developers find it more convenient to work with data-binding frameworks that handle conversions in a generic manner, applicable to a wide variety of applications. One of the main strengths of the Apache Axis2 Web services framework is that it was designed from the beginning to work with a variety of data-binding frameworks. You can pick the data-binding approach that best suits your needs and use that approach to handle conversions between XML and data structures while using the Axis2 framework (and extensions) to handle the actual Web services work.

This article shows you how to use Axis2's data-binding flexibility with sample code for the same Web service implemented using three of the data bindings supported. Find out why you might prefer one of the data bindings over the others.

Link in to Axis2

In the previous article in this series, you learned about the AXIOM document model Axis2 uses for XML message handling. AXIOM differs from other document models in that it supports building the model on demand rather than all at once. When a data-binding framework is used to convert XML to and from application data structures, the data-bound XML is normally only a virtual part of the AXIOM document model. It's not expanded into a full document model unless the model is required for some reason (such as for encryption or signing when using WS-Security).

To isolate applications from needing to work directly with AXIOM, Axis2 supports generating linkage code from Web Services Description Language (WSDL) service descriptions. The generated linkage code handles the details of converting data structures to and from XML using your chosen data-binding framework, giving your application direct access to the data structures. Axis2 also provides some limited support for going in the other direction, generating WSDL from existing code.

Axis2 generates linkage code for both service clients and service providers. The client linkage code is in the form of a stub class, which always extends the Axis2 org.apache.axis2.client.Stub class. The provider (or server) linkage code takes the form of a service-specific implementation skeleton, along with a message receiver class that implements the org.apache.axis2.engine.MessageReceiver interface. Both client and server linkage code generation is handled by the WSDL2Java tool. Next up you'll get a look at the actual linkage code, then go into the details of using the WSDL2Java tool, and finish this section with a quick look at ways to start from existing code.

Client-linkage code

The client-side stub code defines access methods for your application code to invoke service operations. You first create an instance of the stub class, typically using either the default constructor (if your service endpoint is always going to be the same as it was defined in the WSDL used to generate the stub) or a constructor that takes a different endpoint reference as a string. After you've created an instance of the stub, you can optionally use methods defined by the base org.apache.axis2.client.Stub class to configure various features. You can then call the service-specific access methods to actually invoke operations.

Listing 1 shows an example of how to create a stub with the service endpoint changed (from whatever it was in the WSDL) to the default Tcpmon port 8800 on the client system (localhost). Tcpmon is a popular tool for monitoring Web service message exchanges, so having this as an option in your client code is often useful. After the stub instance is created, the timeout value is changed from the default (also useful when debugging provider code because you can easily exceed the standard timeout of 20 seconds), and a service method is invoked.

Listing 1. Client stub usage sample
LibraryStub stub = new LibraryStub("http://localhost:8800/axis2/services/library");
stub.getServiceClient().getOptions().setTimeoutInMilliseconds(10000000);
Types[] types = stub.getTypes();

The Listing 1 code shows a synchronous service method call where the client thread blocks inside the service call and doesn't return until the call is completed and the results are available. Axis2 also supports asynchronous calls, using a callback interface. Listing 2 shows the Listing 1 code modified to use a trivial asynchronous call (trivial in that the application code just waits for the operation to complete rather than doing anything useful).

Listing 2. Client stub asynchronous sample
LibraryStub stub = new LibraryStub("http://localhost:8800/axis2/services/library");
TypesCallback cb = new TypesCallback();
stub.startgetTypes(cb);
Type[] types;
synchronized (cb) {
 while (!cb.m_done) {
  try {
   cb.wait();
  } catch (Exception e) {}
 }
 types = cb.m_result;
 if (types == null) {
  throw cb.m_exception;
 }
}
...
private static class TypesCallback extends LibraryCallbackHandler
{
 private boolean m_done;
 private Types[] m_result;
 private Exception m_exception;
            
 public synchronized void receiveResultgetTypes(Type[] resp) {
   m_result = resp;
   m_done = true;
   notify();
 }
            
 public synchronized void receiveErrorgetTypes(Exception e) {
   m_exception = e;
   m_done = true;
   notify();
 }
}

With an HTTP connection, as used in Listing 2, the response is normally returned immediately to the client. Asynchronous calls are most useful when operating over transports that decouple the request from the response — such as Java™ Message Service (JMS) or Simple Mail Transfer Protocol (SMTP) — because there may be a substantial delay in these cases between the time a request is sent and when the response is received. Of course, services accessed using HTTP may also involve substantial processing delays. For HTTP services with such delays, you can use WS-Addressing to allow decoupled responses, and asynchronous calls are useful for handling these responses.

Besides the stub class (and callback handler class if you generate with asynchronous support), there's also an interface generated for your client code. The interface defines service methods matching the operations defined by a WSDL portType. The stub implements this interface and adds some specialized methods used internally. You can either work with the stub directly, as shown in Listing 1 and Listing 2, or just use the interface to see only those methods that are part of the service definition. Either way, when you call one of the service methods the stub handles converting request data objects to XML, and returned XML to response data objects, using your selected data-binding framework.

If you just want to work directly with XML on the client, you don't need to use a generated client stub class at all; instead you can use the org.apache.axis2.client.ServiceClient class. Doing this means you need to first configure the service and operation, and then call the ServiceClient.createClient() method to create an org.apache.axis2.client.OperationClient for the operation. As an convenience, the WSDL2Java tool (covered later in this article) provides an option to generate a stub class even when you're working with XML directly. The generated stub for this case looks a lot like the data-binding examples, except that it passes an AXIOM element rather than data objects.

Server-linkage code

The server-side linkage code for Axis2 is a message receiver that's defined as part of the Axis2 service configuration. This message receiver must implement the org.apache.axis2.engine.MessageReceiver interface. The interface defines a single void receive(org.apache.axis2.context.MessageContext) method. The Axis2 framework calls this method when a request message is received, and then this method is responsible for handling all the processing of the request (including generating a response, if appropriate).

If you're working with XML directly (in the form of AXIOM elements) you can use one of the standard org.apache.axis2.receivers.RawXML*MessageReceiver classes for the server-side linkage (where the * describes the type of message exchange used by a service). Otherwise, you use a generated message receiver class that adapts between the Axis2 AXIOM-based interface and service code that uses data objects. This service code is generated in the form of a skeleton implementation, with service methods that just throw an exception. You need to add your own code to the skeleton to complete the server-side hookup.

Listing 3 shows a sample of a server-side skeleton (reformatted for readability), with the getBook() method left as generated and the getTypes() method implemented by delegating to an actual implementation class.

Listing 3. Server skeleton sample
public class LibrarySkeleton
{
    private final LibraryServer m_server;
    
    public LibrarySkeleton() {
        m_server = new LibraryServer();
    }
    
    /**
     * Auto generated method signature
     *
     * @param isbn
     * @return book value
     */
    public com.sosnoski.ws.library.Book getBook(java.lang.String isbn) {
        //Todo fill this with the necessary business logic
        throw new java.lang.UnsupportedOperationException("Please implement " +
            this.getClass().getName() + "#getBook");
    }

    /**
     * Get the types of books included in library.
     *
     * @return types
     */
    public com.sosnoski.ws.library.Type[] getTypes() {
        return m_server.getTypes();
    }
}

The downside of adding code directly to this class is that if the service interface changes, you need to regenerate the class and merge your changes. You can avoid this by using a separate implementation class that extends the generated skeleton, allowing you to override the skeleton methods without altering the generated code. To make this work, you need to change the generated services.xml service description. It's easy; just replace the skeleton class name with your implementation class name. The data-binding examples covered later in this article all use the separate implementation class approach. You can see the Ant build.xml files in the Download section for these examples to see how to automate the replacement.


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.

ADB unwrapped

Web services are often developed based on an existing programming API in the form of method calls, and in this case it's convenient to embed the existing API within the Web service. This is easy to do; operations defined for a service (technically for a portType, if anyone wants to nitpick) are essentially equivalent to method calls in an interface definition, after all. The only major difference is that the service defines inputs and outputs as XML messages rather than as call parameters and return values. So to embed an existing API within a Web service definition, you just need a convention for how to represent call parameters and return values as XML message structures.

Fortunately, Microsoft® established a convention in this area early on, saving the rest of us from having to work something out on our own. This convention is called wrapped document/literal and is the default representation .NET uses when exposing a method call as a Web service operation. In essence, this wrapped approach says that each input message is an XML element that consists only of a sequence of child elements, and each output message is an XML element with a single child element. There are some other technical details of the Microsoft implementation that aren't really important except for full .NET interoperability, but these details aside, the messages used for the library example (see Listing 4 for a partial view) follow this approach.

WSDL2Java supports unwrapping such wrapped doc/lit services in ADB code generation. When unwrapping is used with a suitable WSDL service definition, the generated client stub (and also the server skeleton) is simpler and more direct. Listing 6 shows the client application code equivalent to Listing 5, but with the -uw parameter passed to WSDL2Java to generate an unwrapped interface. The message classes that added a layer of complexity in Listing 5 have mostly been eliminated in Listing 6 (with the exception of the GetTypes class), and service methods accept parameters and return results directly rather than embedded in the message classes. Behind the scenes, ADB still generates the message classes and uses them in the generated code, but your code can usually ignore these classes and work with data directly.

Listing 6. ADB unwrapped client code
// create the client stub
AdbUnwrapLibraryStub stub = new AdbUnwrapLibraryStub(target);
        
// retrieve a book directly
String isbn = "0061020052";
BookInformation book = stub.getBook(isbn);
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
TypeInformation[] types = stub.getTypes(new GetTypes());
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 {
  stub.addBook("scifi", isbn, new String[] { "Cook, Glen" }, title);
  System.out.println("Added '" + title + '\'');
  title = "This Should Not Work";
  stub.addBook("xml", isbn, new String[] { "Nobody, Ima" }, title);
  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
BooksByTypeCallback cb = new BooksByTypeCallback();
        
// retrieve all books of a type asynchronously
stub.startgetBooksByType("scifi", cb);
long start = System.currentTimeMillis();
synchronized (cb) {
  while (!cb.m_done) {
    try {
      cb.wait(100L);
    } catch (Exception e) {}
  }
}
System.out.println("Asynchronous operation took " +
  (System.currentTimeMillis()-start) + " millis");
if (cb.m_books != null) {
  BookInformation[] books = cb.m_books;
  ...

Axis2 1.3 update
As this article goes to publication, the Axis2 1.3 release is nearly production ready. The ADB unwrapping issues discussed in this section have been fixed, so if you're able to start working with the Axis2 1.3 code you'll have a much better experience using ADB than in earlier versions of Axis2. A 1.3-specific version of the sample code for this article is included in the Download section.

The main problem with ADB unwrapped support is that it's not yet completely stable. Listing 6 code is appropriate for use with Axis2 1.2 and includes some major improvements from the code that would have been used for ADB unwrapped in Axis2 1.1.1. But the WSDL2Java tool required the WSDL document used with the other examples to be restructured for use in this example, by moving an inline schema for the data classes into a separate schema document. More significantly, the code shown in Listing 6 doesn't all work; the last part of the code, which uses an asynchronous operation, fails at run time with a class cast exception due to an error in the generated ADB client stub code.

By the time the successor to Axis2 1.2 is released, the ADB unwrapped issues should be largely eliminated. But ADB isn't the only data-binding framework for Axis2 supporting unwrapping. JiBX also provides unwrapping support, and the JiBX version has been stable since the Axis2 1.1.1 release. You can take a look at the JiBX client code a little later in this article after checking out the other major choice for Axis2 data binding.

XMLBeans

XMLBeans is a general XML processing framework that includes a data-binding layer. It originated as a BEA Systems project, which was then donated to the Apache Foundation. XMLBeans was the first form of data binding supported by Axis2, and continues to be a popular choice for working with Axis2, especially for use with complex schema definitions.

Listing 7 shows the most interesting parts of the XMLBeans client code for the example application. As with the basic (non-unwrapped) ADB code, there is a separate class for the input and the output of each operation. But XMLBeans differs from ADB in that there's an added class for the document that contains the input or output class (a GetBookDocument in addition to the GetBook class, for example). The net effect is an added layer of object creation when using XMLBeans as opposed to ADB. There's no unwrapping support for XMLBeans in Axis2, so there's no way to avoid this added layer of objects. The result is that XMLBeans-generated classes are somewhat more complex to use than the equivalents for the other data-binding frameworks.

Listing 7. XMLBeans client code
// create the client stub
XmlbeansLibraryStub stub = new XmlbeansLibraryStub(target);
        
// retrieve a book directly
String isbn = "0061020052";
GetBookDocument gbd = GetBookDocument.Factory.newInstance();
GetBookDocument.GetBook gb = gbd.addNewGetBook();
gb.setIsbn(isbn);
gbd.setGetBook(gb);
GetBookResponseDocument gbrd = stub.getBook(gbd);
BookInformation book = gbrd.getGetBookResponse().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
GetTypesDocument gtd = GetTypesDocument.Factory.newInstance();
gtd.addNewGetTypes();
GetTypesResponseDocument gtrd = stub.getTypes(gtd);
TypeInformation[] types =
  gtrd.getGetTypesResponse().getGetTypesReturnArray();
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 {
  AddBookDocument abd = AddBookDocument.Factory.newInstance();
  AddBookDocument.AddBook ab = abd.addNewAddBook();
  ab.setAuthorArray(new String[] { "Cook, Glen" });
  ab.setIsbn(isbn);
  ab.setTitle(title);
  ab.setType("scifi");
  stub.addBook(abd);
  System.out.println("Added '" + title + '\'');
  title = "This Should Not Work";
  ab.setTitle(title);
  stub.addBook(abd);
  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().getAddDuplicate().getBook().getTitle() +
    '\'');
}
        
// create a callback instance
BooksByTypeCallback cb = new BooksByTypeCallback();
        
// retrieve all books of a type asynchronously
GetBooksByTypeDocument gbtd =
  GetBooksByTypeDocument.Factory.newInstance();
gbtd.addNewGetBooksByType().setType("scifi");
stub.startgetBooksByType(gbtd, cb);
long start = System.currentTimeMillis();
synchronized (cb) {
  while (!cb.m_done) {
    try {
      cb.wait(100L);
    } catch (Exception e) {}
  }
}
System.out.println("Asynchronous operation took " +
  (System.currentTimeMillis()-start) + " millis");
if (cb.m_response != null) {
  BookInformation[] books =
    cb.m_response.getGetBooksByTypeResponse().getGetBooksByTypeReturnArray();
  ...

Axis2 1.3 update
As this article goes to publication the Axis2 1.3 release is nearly production-ready. The XMLBeans fault-handling issue has been fixed in Axis2 1.3. A 1.3-specific version of the sample code for this article is included in the Download section.

The Listing 7 client and corresponding server code execute properly with Axis2 1.1.1, but due to a problem in the fault-handling code generation for XMLBeans with release 1.2, it fails at the point of the expected exception when adding a duplicate book ID. This problem should be corrected in the next Axis2 release.

Although XMLBeans claims 100% support of XML Schema, the accuracy of this claim is a matter of interpretation. It's true that for almost any schema construct, XMLBeans generates a set of Java classes that can be used to read and write documents matching the schema. But unlike the other data-binding frameworks covered in this article, XMLBeans by default does little to enforce the schema. For instance, if you comment out the line that sets the title of the book being added in the Listing 7 code, XMLBeans happily reads and writes documents that are missing the required <title> element and are therefore invalid documents. Listing 8 shows this code change along with the XML that was sent to the server for the add request, and the XML that returned from the server when the books were retrieved. In the case of the retrieval response, the XML document includes the <title> element but uses an xsi:nil="true" attribute, which is not permitted by the schema, and is again invalid.

Listing 8. XMLBeans client and invalid documents
      AddBookDocument abd = AddBookDocument.Factory.newInstance();
      AddBookDocument.AddBook ab = abd.addNewAddBook();
      ab.addAuthor("Cook, Glen");
      ab.setIsbn(isbn);
      ab.setType("scifi");
//            ab.setTitle(title);
      System.out.println("Validate returned " + abd.validate());
      stub.addBook(abd);
      ...

  <addBook xmlns="http://ws.sosnoski.com/library/wsdl">
   <type>scifi</type>
   <isbn>0445203498</isbn>
   <author>Cook, Glen</author>
  </addBook>
    
  <getBooksByTypeResponse xmlns="http://ws.sosnoski.com/library/wsdl">
   ...
   <getBooksByTypeReturn isbn="0445203498" type="scifi">
    <author xmlns="http://ws.sosnoski.com/library/types">Cook, Glen</author>
    <title xmlns="http://ws.sosnoski.com/library/types" xmlns:xsi="http://www.w3.org/2001
      /XMLSchema-instance" xsi:nil="true"/>
   </getBooksByTypeReturn>
  </getBooksByTypeResponse>

This is a simple case of not setting a required value. For more complex schemas, the XMLBeans-generated API can conceal even more pitfalls. A recent discussion on the XMLBeans users e-mail list dealt with a case where values had to be added to two different lists in alternating order to generate correct output. So XMLBeans requires developers to understand both the schema and how the generated code relates to the schema to assure valid XML documents are constructed by application code. One of the main benefits of data-binding frameworks is normally to hide this kind of schema detail from the developers, and XMLBeans certainly falls short on this front.

You can avoid the issues of processing or generating invalid XML documents with XMLBeans by calling the validate() method included in the generated classes. If you're working with XMLBeans, you should at least use this check for all documents during testing and development. However, validation has a considerable impact on performance (and as you'll see in the next article in this series, XMLBeans is already slow even without calling validate() on every document), so many applications should avoid the overhead of validation in production deployments. Validation is also rather limited in terms of the result information. To isolate the cause of a validation failure, you should run a standard schema validation on the document in error.

JiBX

JiBX (which I also developed) is a data-binding framework that's mainly focused on working with existing Java classes rather than code generation from schema. With JiBX, you first create a binding definition to define how Java objects are to be converted to and from XML, then compile that binding using a tool that enhances your data class files by adding methods (as bytecode) implementing the conversions. The JiBX runtime framework then uses these added methods to convert data to and from XML.

The JiBX approach offers some unique strengths and weaknesses. On the plus side, JIBX lets you work directly with existing classes in cases where Web service interfaces are being added to existing service code. The Jibx2Wsdl tool is especially useful for this purpose, because it generates everything you need to easily deploy your existing code as an Axis2 service. You can define multiple bindings for the same classes to work with different XML versions of documents simultaneously, using a single data model. By modifying the bindings, you can generally even keep the same XML representation as your data classes are refactored.

Listing 9 shows the interesting parts of the JiBX client code, using classes matching the message elements. This code is similar to the ADB equivalent shown in Listing 5, so I won't go over it in detail. The only notable difference is that because both the data classes and the message classes are under the control of the user, it's easy to add convenience constructors (as with the AddBookRequest) and other support methods to the classes you're using with JiBX.

Listing 9. JIBX client code
// create the server instance
JibxLibraryStub stub = new JibxLibraryStub(target);
        
// retrieve a book directly
String isbn = "0061020052";
GetBookResponse bresp = stub.getBook(new GetBookRequest(isbn));
Book book = bresp.getBook();
if (book == null) {
  System.out.println("No book found with ISBN '" + isbn + '\'');
} else {
  System.out.println("Retrieved '" + book.getTitle() + '\'');
}
isbn = "9999999999";
bresp = stub.getBook(new GetBookRequest(isbn));
book = bresp.getBook();
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 tresp = stub.getTypes(new GetTypesRequest());
Type[] types = tresp.getTypes();
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 {
  AddBookRequest abr = new AddBookRequest("scifi", isbn, title,
    new String[] { "Cook, Glen" });
  stub.addBook(abr);
  System.out.println("Added '" + title + '\'');
  title = "This Should Not Work";
  abr = new AddBookRequest("scifi", isbn, title,
    new String[] { "Nobody, Ima" });
  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
BooksByTypeCallback cb = new BooksByTypeCallback();
        
// retrieve all books of a type asynchronously
stub.startgetBooksByType(new GetBooksByTypeRequest("scifi"), 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) {
  Book[] books = cb.m_response.getBooks();

Listing 10 shows the equivalent JiBX unwrapped code. As with the ADB unwrapped code, it's much easier to understand and work with the unwrapped form of the service calls than with the direct code. The only significant difference between the JiBX and the ADB versions is that in the JiBX case you don't need to create an object when no values are being passed, as was required by ADB for the getTypes() call. The JiBX unwrapped support is also somewhat more stable than the ADB version, because it has been fully supported since Axis2 1.1.1.

Listing 10. JIBX unwrapped client code
// create the server instance
JibxUnwrapLibraryStub stub = new JibxUnwrapLibraryStub(target);
        
// retrieve a book directly
String isbn = "0061020052";
Book book = stub.getBook(isbn);
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
Type[] types = stub.getTypes();
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 {
  stub.addBook("scifi", isbn, new String[] { "Cook, Glen" }, title);
  System.out.println("Added '" + title + '\'');
  title = "This Should Not Work";
  stub.addBook("xml", isbn, new String[] { "Nobody, Ima" }, title);
  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
BooksByTypeCallback cb = new BooksByTypeCallback();
        
// retrieve all books of a type asynchronously
stub.startgetBooksByType("scifi", cb);
long start = System.currentTimeMillis();
synchronized (cb) {
  while (!cb.m_done) {
    try {
      cb.wait(100L);
    } catch (Exception e) {}
  }
}
System.out.println("Asynchronous operation took " +
  (System.currentTimeMillis()-start) + " millis");
if (cb.m_books != null) {
  Book[] books = cb.m_books;

JiBX unwrapped support also differs from ADB in terms of the classes used. When using ADB unwrapped, classes are still generated and used behind the scenes for all the message elements. With JiBX, classes must be defined corresponding to the message elements when using the direct handling, as in Listing 9; for unwrapped handling, only the classes passed as values need to be defined and included in your binding definition. Either way, the JiBX binding definition needs to be created before you run the Axis2 WSDL2Java tool and needs to be passed in using an -Ebindingfile command-line parameter.

The biggest downside to the JiBX binding approach, at least in Web service terms, is probably that JiBX currently provides weak support for working from an XML schema definition. Even this weak support for working from schema, in the form of the Xsd2Jibx tool, isn't integrated into the Axis2 WSDL2Java code generation. This means that you need to have your Java data classes and binding definition created before you can run WSDL2Java to generate the Axis2 linkage code. The bytecode enhancement step required by JiBX can also be problematic in some environments, because it generally needs to be done at application build time and results in code being present in the classes for which no source code is available.

JiBX data binding offers some unique benefits, as discussed at the beginning of this section. In terms of Axis2 usage, JiBX also provides an advantage over the other frameworks in that it's supported with bug-fix releases that can be plugged into Axis2 releases to correct problems found after the release (see Get products and technologies in the Resources section for details). For the other frameworks, the only way to obtain bug fixes is by moving to the nightly builds of Axis2, which can often lead to other problems. In the future, JiBX is expected to offer solid code and binding generation from schema support. When this is available, JiBX should be an excellent all-around data-binding alternative to Axis2. Until then, it's of most benefit when working from existing Java code, where the Jibx2Wsdl tool provides excellent support.


Summary

Axis2 currently provides full support for three different data-binding frameworks:

  • ADB is designed specifically for Axis2 and can only be used in the Axis2 environment. As of the Axis2 1.3 release, it provides good — and growing — support for code generation from schema. It also supports convenient unwrapped service methods and automatic attachment handling, making it a top choice when working from existing WSDL service definitions.
  • XMLBeans provides the most complete support for modeling schema structures in generated Java code. But it creates the most complex Java model for a given schema and doesn't support unwrapped service methods for a simpler interface. By default, it also doesn't enforce even basic schema structure rules on either input or output XML documents, making it easy for invalid XML documents to be accidentally created or accepted for processing.
  • JiBX is the only alternative that supports working with existing Java classes. It also provides excellent support for unwrapped service methods. But the JiBX support integrated into Axis2 doesn't handle code generation from schema, and even the separate code generation from schema support provided by the JiBX tools currently provides only limited schema support.

It's great that Axis2 offers a choice between these data-binding frameworks, because there's no single best choice for all needs. In the future, Axis2 will also provide full support for JAXB 2.0, which I'll cover as part of this series in an upcoming article about the related JAX-WS 2.0. Knowing the strengths and weaknesses of each framework can help you make the best choice for your needs and alert you to potential problem areas before you find them in production. In the next article in this series, you learn about another aspect of comparison for the Axis2 data-binding frameworks: performance.


Downloads

DescriptionNameSize
Sample code (Axis2 1.2 and earlier)j-java3code.zip82KB
Sample code (Axis2 1.3)j-java3code1_3.zip87KB

Resources

Learn

Get products and technologies

  • Get more information about and download Axis2 to try it out for yourself. Full support for ADB and XMLBeans data binding is included in the download.
  • Download the JiBX data-binding framework, which lets you adapt existing code for use with Axis2 or other XML applications.
  • Innovate your next development project with IBM trial software, available for download or on DVD.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, SOA and web services, Open source
ArticleID=390752
ArticleTitle=Java Web Services: Axis2 Data Binding
publish-date=07262007