Axis-orizing objects for SOAP

Go from Java objects to a SOAP Web service with Apache Axis

It all starts innocently enough. You have an object-oriented Java interface you want to turn into a Web service. You come across Axis, the Apache framework for publishing and consuming Web services using the XML-based SOAP (Simple Object Access Protocol) specification. Soon enough you find yourself needing to become more of a SOAP expert than you had hoped, opening the Axis source more than you have patience to, and, well, spending more time than you have. At least that's what happened to me. This article explains how to transform your Java objects for the Web-service world with Axis—minus the hassle.

First steps

First, make sure you have installed:

  • A servlet engine or J2EE (Java 2 Platform, Enterprise Edition) application server. The Axis server component is typically deployed as a Web application.
  • A programmable build tool like Apache Ant. Creating a Web service takes you through a string of deployment operations besides just compiling, so it's critical that you automate this process as fully as possible.
  • Axis 1.1 Release Candidate 2. Download the archive and expand it. Follow the installation instructions at least up to "Validate Axis with happyaxis."
  • Optionally, an XML parser. For example, a DOM (Document Object Model) parser like JDOM or dom4j. At times, you will need to edit automatically generated XML files. If you're willing to write some code using a DOM (or SAX (Simple API for XML)) parser, you can automate this work.
  • Optionally, JUnit. Later, I discuss testing an Axis Web service via a JUnit client.

SOAP and objects

While this article doesn't purport to be a general primer on SOAP best practices, some words on SOAP hygiene are in order.

SOAP originally stood for Simple Object Access Protocol. Yet, SOAP never mandated the use of objects per se, so the acronym eventually was cleansed of its origin. Don't let that fact throw you off the scent. Axis still converts your Java objects into XML for transmission over some transport, typically HTTP. In SOAP-formatted XML, these Java objects appear as data structures, which can be built through what are effectively class inheritance and object composition, to borrow a couple object-oriented terms.

Of course, when you get into sending complex, self-defined types over SOAP, you can always ask yourself the question, "Do I really need to send these structures?" You will find your answer with a Java interface.

Java interface as service contract

You must define whatever methods you want to provide through your Web service in a single interface or class. Interface is preferred over class, not only so that the service contract does not depend on its implementation, but also so that Axis and its tools don't inadvertently publish extra methods or types.

For a Web service-worthy interface, minimize the interaction between client and server, in terms of both frequency and bandwidth. Even if the client and service are on the same machine, you pay the minor performance price of having to marshal and unmarshal each request and response into relatively bulky XML text, as opposed to binaries.

In fact, instead of wondering if you really need to send complex types, you might start wondering whether you should create a SOAP Web service at all. If you can be assured that your client will always be Java, then perhaps stick to Java RMI (Remote Method Invocation). Not only do Java binaries make for faster throughput, you're also spared the difficulties the rest of this article tries to address.

But let's suppose you do need the flexibility SOAP affords. And let's suppose you're adhering to the Model-View-Controller (MVC) paradigm and not passing around data structures willy-nilly. Even after you've trimmed the fat from your Web-service interface, chances are it will still define some custom objects. Throw in some strings, integers, and the like, maybe some arrays, and you have the Java types that must translate into XML types.

Building blocks

The nice thing is Axis translates for you, scouring the methods in your interface for data types, both primitives and classes. Axis maps between Java types and XML Schema types. The XML Schema specification, put forth by the World Wide Web Consortium (W3C), establishes a system of structure-ish data types that XML can express.

Axis does its mapping according to another spec, JAX-RPC. The Java API for XML-based Remote Procedure Call dictates how Java should interact with XML-based protocols like SOAP. JAX-RPC specifies mappings for the following Java primitives: boolean, byte, short, int, long, float, and double. Don't worry about the corresponding XML Schema types. With Axis, what's important is knowing that a Java type gets mapped, not what it gets mapped to.

You can also use the object wrappers for any of these primitive classes, for example java.lang.Integer for int. JAX-RPC provides the same mapping, but with an extra XML attribute allowing a null value to be passed.

JAX-RPC also specifies mappings for the following standard Java classes:

  • java.lang.String
  • java.math.BigInteger
  • java.math.BigDecimal
  • java.util.Calendar
  • java.util.Date

Taken together, the primitive Java types, their object equivalents, and the standard classes like String can be considered building blocks. The types that your Java interface exposes, through its methods' return types and parameters, must be one of the following:

  • Building-block types.
  • Classes or arrays that are ultimately reducible to building blocks. Classes and arrays act effectively as containers.
    • Classes: JAX-RPC prescribes that a class conform to the JavaBeans convention: public default constructor, public fields, and/or get/set/is methods. Properties become child elements (object composition). A class can extend other classes, picking up properties along the way (class inheritance).
    • Arrays: JAX-RPC mandates that a Java array literally be an array, e.g., String[], not a java.util collections type, like ArrayList or Vector.

Your objects and arrays can recursively contain other objects and arrays, but eventually Axis needs to reach building blocks. Axis will complain if it unwraps all the packaging and doesn't find a gift inside. Likewise, Axis will complain if it picks up any superfluous public properties that can't reduce to building blocks.

Now you can see why java.util.ArrayList does not work. An ArrayList contains any number of java.lang.Object instances, but Object is not a building block.

Actually, Axis will blithely process an ArrayList or any other Java collection. And if you can figure out the secret handshake, you might even be able to get it to work. At this point, though—to put it bluntly—Axis is freelancing, and you should not. If you limit your interface and its supporting classes to publishing only literal arrays, you avoid compromising your Web service code's JAX-RPC conformance, and, with that, your code's portability and maintainability.

The upshot of JAX-RPC's restrictions is that, with SOAP, your Java interface and its associated classes might have to put on a somewhat different public face than they would if you were using Java RMI. For example, if one of your XML-mapped classes has a public List member, you might find yourself having to add a helper like the following:

SomeStuff[] toArray() {
   List all = getValuesHelper();
   return (SomeStuff[])all.toArray(new SomeStuff[all.size()]);
}

Oh, and one other thing: it is perfectly valid for your Web service's methods to throw exceptions. More on that later.

WSDL and WSDD

Time to bring in SOAP's companion XML format, the Web Services Description Language (WSDL). To understand the relationship between SOAP and WSDL, imagine a restaurant. The SOAP request is the order the server jots down and brings to the kitchen. The SOAP response is the food served. The WSDL document is the menu.

WSDL defines custom XML types, the operations (methods) the Web service serves, the request and response messages tied to each method, and the URL to which the service is bound. When Axis (literally, one of its associated tools) combs a Java interface and its associated classes, the output is a static WSDL file. As you will see, this particular *.wsdl file plays a transitional role. The deployed and running Axis Web application can also generate a *.wsdl file on the fly to answer a request.

Don't confuse WSDL with Axis's own proprietary XML format, WSDD (Web Service Deployment Descriptor). A WSDD file is where Axis tracks operations and—unlike WSDL—type mappings. During the course of deployment, Axis produces three WSDD files:

  • deploy.wsdd: A transitional file representing a single Web service; has instructions for deploying the Web service to the Axis Web application
  • undeploy.wsdd: Only used to remove a previously deployed Web service
  • server-config.wsdd: Appears in the running Axis Web application's WEB-INF directory; represents any number of deployed Web services

With these various files in mind, we can look at the major steps in deploying a typical Axis Web service:

  1. Copy the Axis Web application to your application server (I call this step 0 because it's such a "one-time-and-forget-it" item).
  2. Compile server-side, service classes.
  3. From the service classes, generate the static WSDL file.
  4. From the static WSDL file (not from the service classes), generate deploy.wsdd, plus Java source for client-side proxy classes.
  5. Using deploy.wsdd, deploy the Web service to a running Axis Web application, creating or modifying server-config.wsdd.
  6. From the Java source generated in Step 3, compile the client-side proxy classes. At this point, you might also compile test classes that invoke the proxy classes.

Steps 4 and 5 are interchangeable.

Figure 1 illustrates this sequence through the files involved. It also illustrates how so (relatively) little source produces so much output.

Figure 1. A flowchart expressing the major steps in Axis deployment through the associated files. Click on thumbnail to view full-size image.

Looks easy enough, right? Well, the devil's in the details.

Set up

This article follows a project for a demonstration "outline" Web service. For this Web service, we deploy Axis's own Web application, although you could also use Axis within an existing Web application.

We start by examining the two directory levels (shown in Figure 2) just under the project root to get an idea of the various types of source and output files involved.

Figure 2. The top two directory levels for a sample Axis Web service project
  • source

    • ant: Where build.xml and any other build files can go.
    • java: Java server source, plus source for test classes that can be used with an Axis client. All Axis client-proxy source is auto-generated.

  • output

  • axisAutoSource: Where outline.wsdl will be deposited. Also the destination for auto-generated client source and deploy.wsdd, which will show up in directories reflecting the server-side package structure.
  • axisClientClasses: The destination for compiling the client source in axisAutoSource.
  • axisServerClasses: A temporary destination for compiling the Java server source. Compiled classes must ultimately go in the Axis Web application.
  • serverDeploy: The application server containing the Axis Web application.

The source directory contains only the original source specific to the outline project. The source for Axis and other tools is stored elsewhere.

If you're using Apache Ant to build your project, you can use some Ant tasks predefined in the Axis distribution. Add the following reference to your build.xml: <taskdef resource="axis-tasks.properties"/>. You don't need to specify an absolute path to the properties file. Axis and Ant will find it.

Those predefined Ant tasks—and the tools they invoke—will fail unless their classpath includes the following archives, all of which are located in the Axis install's lib directory:

  • axis.jar
  • axis-ant.jar: Only needed if you're building with Axis's custom Ant tasks
  • commons-discovery.jar
  • commons-logging.jar
  • jaxrpc.jar
  • saaj.jar
  • wsdl4j.jar

Set the classpath for the Axis taskdef, either through a nested element or an attribute. Example:

<taskdef resource="axis-tasks.properties" classpath="${classpathConcatenated}"/>

The needed jars are redundantly stored in the Axis install's webapps/axis/WEB-INF/lib.

Destination: WSDL

Now we can go through the previously listed deployment steps in more detail.

  1. Copy the Axis Web application to your application server: Copy webapps/axis and its contents to your application server's webapps directory and do any other necessary application server configuration. Customize the Axis web.xml as you wish.
  2. Compile service classes: Whatever your output directory for the compiler command, the classes obviously must ultimately get to the Axis Web application's lib or classes directory.
  3. Generate the static WSDL file: For this step, you use a Java application that comes with the Axis distribution, org.apache.axis.wsdl.Java2WSDL, or an equivalent Ant task.

First, a few words on XML namespaces, which are used to qualify names and avoid collisions. In this respect, they resemble Java packages, and, in fact, you map packages to namespaces. Namespace paths should use a reverse order of significance, like an Internet domain. So the package foo.bar would map to urn:bar.foo. The urn prefix signals a Uniform Resource Name, a presumably unique name that doesn't depend on its location.

Here's a sample command-line invocation of Java2WSDL (assuming the classpath has already been set):

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more