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):

java org.apache.axis.wsdl.Java2WSDL 
-o c:\projectRoot\output\axisAutoSource\outline.wsdl 
-l http://localhost:8080/axis/services/outline -n urn:outline.demo  
-pdemo.outline urn:outline.demo demo.outline.OutlineComponent 

Table 1 shows a breakdown of the command-line arguments.

Table 1. Typical command-line arguments for Java2WSDL

Switch or argumentWhat it means
-o filepathOutput—the file path to the WSDL file being generated
-l URLLocation—the URL where clients will locate the service
-n namespaceWSDL's target namespace
-p package namespacePackage-to-namespace mapping
TargetInput Java interface

Here's how Ant would express the same action, with Ant properties where appropriate:

<axis-java2wsdl 
         output="${axisAutoSourceDir}\${WSDLFILE}"
         location="http://localhost:8080/axis/services/outline"
         namespace="${NAMESPACE}"
         classname="demo.outline.OutlineComponent">
   <mapping namespace="${NAMESPACE}" package="demo.outline"/>
</axis-java2wsdl>

Suppose that, while combing for objects referred to by demo.outline.OutlineComponent, Java2WSDL picks up a class in a package, demo. The generated WSDL creates a second namespace, http://demo. No, this is not a machine on a local network; no, this value is not likely to break anything. Still, better to explicitly specify an additional namespace, that is, -pdemo urn:demo.

If you want to invoke Java2WSDL from within your own class, read the sidebar, "Call Java2WSDL and WSDL2Java from Another Java Class."

Axis interprets service-originated exceptions as SOAP-specific Fault elements. The fault contains a faultcode with the exception class's full name. Within your custom exception class, define any other bean-conformant subelements you want Java2WSDL to pick up. If you want to throw exceptions and you're deploying with a Java 1.4+ VM, you must use Axis 1.1 Release Candidate 2 or later. Earlier versions of Java2WSDL get thrown off by some unbeaniness introduced to the Throwable class with Java 1.4.

Origin: WSDL

Now you're up to Step 3: Generate deploy.wsdd, plus Java source for client-side proxy classes.

With this step, the input is the WSDL that was the previous step's output. So instead of Java2WSDL, you use another Axis application, WSDL2Java. Here's a sample command-line invocation:

java org.apache.axis.wsdl.WSDL2Java 
-o c:\projectRoot\output\axisAutoSourceDir
-d Application -s 
c:\projectRoot\output\axisAutoSourceDir\outline.wsdl

Table 2 shows a breakdown of the command-line arguments.

Table 2. Typical command-line arguments for WSDL2Java

Switch or argumentWhat it means
-o directorypathOutput—the parent directory where proxy classes and WSDD files will be placed
-d scopeDeploy scope—Application, Request, or Session
-sServer side—emit WSDD files
TargetInput WSDL file

Here's how to do the same work in Ant:

<axis-wsdl2java
      output="${axisAutoSourceDir}"
      deployscope="Application"
      serverside="true"
      url="${axisAutoSourceDir}\${WSDLFILE}">
</axis-wsdl2java>

The generated client classes appear in a directory/package structure reflecting that of the original service classes. You might ask, "What if I don't want to generate proxy classes?" WSDL2Java must. Just ignore them, though you probably shouldn't.

It should be clear now how to create an Axis client for an existing Web service. Just point WSDL2Java or axis-wsdl2java to the URL for the Web service's WSDL. (The Axis Web application will dynamically generate a WSDL when a request adds a ?wsdl argument to the service's location.) As long as you specify the server-side -s option, WSDL2Java will also produce deploy.wsdd and undeploy.wsdd. These XML files will show up in the package directory corresponding to your target namespace, for example, demo/outline.

With the server-side option, WSDL2Java also generates Java source files for:

  • A new interface effectively identical to your original service interface
  • That interface's implementation with empty method bodies and a name in the form ServiceNameSoapBindingImpl

Why does WSDL2Java bother? Because it doesn't know that you have already written a Java service interface and implementation. Because its only input (as the name implies) is the WSDL file.

Accordingly, deploy.wsdd specifies *SoapBindingImpl as the service implementation class. So edit the deploy file to make it refer to your actual service implementation. Here's the XPath expression for the element in question: /deployment/service/parameter[@name="className"]. You'll want to change the value of another attribute in this element, coincidentally named value, to the fully qualified name of the class that implements your service interface. This is where an XML parser comes in handy: you could program your build to have the parser automatically change this value.

Deployment: The rest

  1. Deploy the Web service to a running Axis Web application: With your application server running, launch another Axis application, org.apache.axis.client.AdminClient. This application takes a single argument, deploy.wsdd's full file path.

  2. Compile the client-side proxy classes: You could exclude from the compiler command the server source spewed by WSDL2Java; however, you don't need to.

While an Axis/Java client may not be a requirement, it proves invaluable for testing. So at this stage, you might also want to compile any specially written test classes. As for writing them, read on.

Test an Axis Web service

When you talk about JUnit-testing an Axis Web service, you're probably talking about testing a Web service that just happens to be running on Axis; JUnit actually interacts with an Axis client. And for JUnit-Axis client testing, the obvious approach starts by running WSDL2Java with the --testCase argument. The result is a ServiceNameTestCase.java file with a test*() method for each Web service method.

Alternatively, I describe a home-cooked solution for testing an Axis Web service with JUnit. It follows two design strategies:

  • Encapsulate the choice of whether the service code is being called locally or remotely (via SOAP)
  • Each test method constitutes a use-case potentially involving a sequence of (Web service) method calls

The client side features two test classes:

  • The actual class that extends JUnit's junit.framework.TestCase and has test*() methods.
  • A helper class that actually calls the proxy classes generated by WSDL2Java. The TestCase subclass's test*() methods call corresponding methods in this class. For instance, a testUseCaseA() would call useCaseA() here.

This level of indirection allows the TestCase subclass to concentrate on duties directly related to testing, like checking assertions.

These two classes are also the same ones used to test the Web service component locally, not as a Web service. A flag decides whether to test remotely or locally.

A third strategy concerns the client-server relationship. Between each test, JUnit will automatically reinitialize your client, but not the service, which runs independently of JUnit. If you check your server data at the end of each test and run more than one test in sequence, the second and later tests will likely fail because they accumulate data from preceding tests rather than generate fresh data for each current test. This means that, for each test, you must manually reinitialize your Web service. One way to accomplish that is to add to your Java interface a reinitialize operation. Then have the client call that operation at the start of each test.

Now let's look at the code. The helper class has a member that instantiates the Web service interface: private OutlineComponent mProxy;. Each helper use-case method starts by calling the following method:

private void resetOutlineComponent() throws Exception {
   if (mIsSOAP) {
      // Obtain a service handle using WSDL2Java-generated 
      // client classes.
      OutlineComponentService service = 
            new OutlineComponentServiceLocator();
      // Now use the service handle to get an implementation of the
      // service interface. The lowercase 'outline' is based on the 
      // location argument to Java2WSDL.
      mProxy = service.getoutline();
      // Reinitialize server data.
      mProxy.purge();
   }
   else
      ...
}

You can see from the method above that only two lines of code are required to obtain a proxy with which you can call Web service methods just as if they were available locally.

To invoke the graphical JUnit test-runner (assuming the classpath includes junit.jar on top of everything else), call java junit.swingui.TestRunner -noloading. You need the -noloading switch to avoid an ugly classloading issue between JUnit and Axis.

Finally, no discussion of testing Axis Web services would be complete without a mention of TCP Monitor (or tcpmon), a utility that comes with the Axis installation. As the name suggests, tcpmon has a potentially broader use than debugging Axis Web services. It intercepts TCP requests and reroutes them to a server; it then passes responses back to the original client. The contents of request-response pairs display in a graphical window.

To use tcpmon, choose a port on which it will listen and make sure the client calls that port. For example, suppose the servlet engine uses port 8080. So use 8070. Reinvoke Java2WSDL, passing a different location argument: -l http://localhost:8070/axis/services/outline. Revisit subsequent deployment steps.

When launching tcpmon, pass it three arguments: listen port, target hostname, and target port. Without classpath, that would be java org.apache.axis.utils.tcpmon 8070 localhost 8080. Thus, when testing an Axis Web service, you launch three applications: application server, TCP Monitor, and JUnit or another client.

General knowledge and Axis knowledge

Viewed at a high level, this article really describes a three-part procedure:

  1. Develop an existing object-oriented Java interface into a Web service interface
  2. Deploy this interface and its supporting classes as an Axis Web service
  3. Develop and deploy a client based on the Web service

As you can see, the first part is easy. Once you realize the relationship between Java objects and their XML type representations, the JAX-RPC rulebook is almost common sense. And you can apply this conversion to any JAX-RPC-compliant Web service engine.

As for creating a service and client on Axis, okay, those are the tricky parts. Still, don't discount the body of Axis information available. Besides this article, you have Axis's documentation anchored by a user's guide, and the heavily trafficked axis-user mailing list, which is archived. For those who still need a clear high-level explanation of Axis deployment and some guidance to help you steer clear of the all-too-frequent pitfalls, hopefully, this piece has provided such a guide for you.

Mitch Gitman is currently documenting a Web services security implementation for a major system and tools vendor. His other development efforts focus on J2EE (Java 2 Platform, Enterprise Edition)/.Net integration and XML optimization. Previously, he worked as a Java server developer for a legacy integration vendor. After earning a BS in computer science in 1999 from the University of Arizona, Mitch migrated northwest in search of rain.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies