Previous Section Next Section

WSDL and Java

In this section, we review the relationship between Java and WSDL. As you will see in Chapter 8, "Interoperability, Tools, and Middleware Products," many Web services development and/or runtime environments include WSDL tooling support. We review the tooling support in Axis (alpha 2 release) for generating service implementation code based on a WSDL, and the tooling support for generating a WSDL document from a Java implementation of a service.

Deriving Code from WSDL

Because WSDL is an IDL-level service description language, it is a good idea to make sure that it accurately represents the service it describes. You have two choices: Either you can manually design and code the service based on your understanding of a WSDL document, or you can use a tool to generate as much of the code for the service as possible.

Al Rosen used the WSDL2java program in Axis to generate the code implementing the priceCheck service. Al entered the following in his command line:

java org.apache.axis.wsdl.Wsdl2java --verbose --skeleton --messageContext  --
package ch6.ex1
   --output c:\book\ch6\ex1c:\book\ch6\ex1\pricecheck.wsdl

The output of the command was:

Parsing XML File: c:\book\ch6\ex1\pricecheck.wsdl

Using package name: ch6.ex1
Generating portType interface: PriceCheckPortType.java
Generating server-side PortType interface: PriceCheckPortTypeAxis.java
Generating client-side stub: PriceCheckSOAPBindingStub.java
Generating server-side skeleton: PriceCheckSOAPBindingSkeleton.java
Generating type implementation: AvailabilityType.java
Generating type implementation holder: AvailabilityTypeHolder.java
Generating service class: PriceCheckService.java
Generating deployment document: deploy.xml
Generating deployment document: undeploy.xml

Let's take a look at each one of these files generated by the WSDL tooling in Axis and discuss how these files were generated from the WSDL definition of the priceCheck service. We will also discuss the steps Al Rosen took to quickly get the priceCheck service running and tested.

Overview of the Files Generated by WSDL2Java

The Axis WSDL tooling generates files for use on the client and the server. For the client, Axis generates three files:

  • An interface definition for each portType element (PriceCheckPortType.java in our case)

  • A client-side stub class for each binding element (PriceCheckSOAPBindingStub.java in our case)

  • A factory-pattern style class for each service (PriceCheckService.java in our case)

The intention of the portType-based interface is to specify the interface the client application should use to interact with the Web service via the client-side stub. The binding-based client-side stub is a proxy for the Web service in the client's programming language and runtime environment. The service-based factory class returns a client-side stub instance complete with a network endpoint address.

For the server, Axis generates three files:

  • A modified interface file for each portType, specific for use by the Axis engine (PriceCheckPortTypeAxis.java in our case)

  • A server-side skeleton based on each binding element (PriceCheckSOAPBindingSkeleton.java in our case)

  • A server-side "empty" service implementation file for each binding element (PriceCheckSOAPBindingImpl.java in our case)

The server-side interface is generated only if the --messageContext option is used (we'll talk about this option a little later). Otherwise, the server-side interface is the same as the client-side interface. This interface specifies the interface the server expects the target Web service to implement. The server-side skeleton is invoked by the Axis engine and is responsible for invoking the target Web service. The extra level of indirection is not needed in many cases; however, when there are in/out and multiple out parameters, the server-side skeleton comes into play.

For each complex type defined, two encoding classes are generated: one defining a basic Java implementation of the structure defined by the complex type (AvailabilityType.java in our case) and a class to implement RPC in, in/out, and out parameter semantics (AvailabilityTypeHolder.java in our case).

Finally, the Axis tooling generates a deployment descriptor file called deploy.xml that can be used to deploy the service to an Axis server. A symmetric undeploy.xml is generated to undeploy the service from the Axis server.

Let's take a look at the details of the files generated by the Axis WSDL tooling from the priceCheck.wsdl example.

Generating Classes for Serialization/Deserialization

The priceCheck.wsdl file defined one complex type structure:

<!-- Type definitions -->
<types>
   <xsd:schema targetNamespace="http://www.skatestown.com/ns/availability"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:complexType name="availabilityType">
         <xsd:sequence>
            <xsd:element name="sku" type="xsd:string"/>
            <xsd:element name="price" type="xsd:double"/>
            <xsd:element name="quantityAvailable" type="xsd:integer"/>
         </xsd:sequence>
      </xsd:complexType>
   </xsd:schema>
</types>

From the availabilityType complex type, the AvailabilityType.java encoding class is generated. Because only one complex type element is defined in priceCheck.wsdl, only one encoding class is generated.

The AvailabilityType.java class defines a straightforward Java representation of the availabilityType complex type. The name of the class is derived from the value of the name attribute of the complexType element. The class itself defines one public instance variable for each of the child elements in the type:

public class AvailabilityType implements java.io.Serializable {
    private String sku;
    private double price;
    private int quantityAvailable;

The names of the instance variables are from the names of the child elements of the availabilityType complex type.

The Axis WSDL tooling also generates a public no-arg constructor

public AvailabilityType() {
}

and a public constructor containing parameters for each of the instance variables generated:

public AvailabilityType(String sku, double price, int quantityAvailable) {
    this.sku = sku;
    this.price = price;
    this.quantityAvailable = quantityAvailable;
}

Finally, the Axis WSDL tooling generates a pair of accessor methods (one get method and one set method) for each instance variable. For example, the accessor methods for the sku instance variable are as follows:

public String getSku() {
    return sku;
}

public void setSku(String sku) {
    this.sku = sku;
}

That's it for the encoding class for AvailabilityType. The other Java classes generated by the Axis WSDL tooling use this class to manipulate availabilityType elements. This class is also used as part of the BeanMapping graphics/book.gif deployed with this service. We will discuss this a little later in the section on generating the deployment XML.

Another file was generated for the availabilityType complex type: AvailabilityTypeHolder.java. This file is used to implement the behavior of in/out and multiple out parameters in an RPC-style Web service. We'll examine the role of holder graphics/book.gif classes when we examine the in/out and multiple out parameters in more detail. As it turns out, priceCheck does not have any in/out parameters and only has one out parameter, so the AvailabilityTypeHolder.java file is not used.

As of the Axis alpha-2 release, the Axis WSDL tooling is at a very early stage. The Axis WSDL tooling will generate encoding classes for complex types and even handle elements referencing other complex types. Axis supports the common simple types from XML Schema (but not all of the simple types) and does not support the use of the XML Schema extension mechanism to base new types from existing types. Axis WSDL tooling also does not support nesting of complex types.

Generating the Client Stub Factory, Stub, and Interface

The client stub is a class that encapsulates the details of the SOAP and networking protocol layers from the application. The client stub presents an interface based on the portType to the application. The application does not need to know anything about SOAP, HTTP, or any of those lower-level details. The application invokes the interface and retrieves a response in terms of Java method invocation and Java data types.

The interface generated for the priceCheck portType is shown here:

public interface PriceCheckPortType extends java.rmi.Remote {
    public AvailabilityType checkPrice(String sku)
         throws java.rmi.RemoteException;
}

The interface closely reflects the portType definition:

<!-- Port type definitions -->
<portType name="PriceCheckPortType">
   <operation name="checkPrice">
      <input message="pc:PriceCheckRequest"/>
      <output message="pc:PriceCheckResponse"/>
   </operation>
</portType>

The name of the interface class is derived from the name of the portType. The only method signature, checkPrice, reflects the fact that only one operation, checkPrice, was defined for the portType. The signature of the method reflects the input, output, and fault elements of the operation. The input parameter, a String parameter named sku, reflects the message definition for the PriceCheckRequest:

<message name="PriceCheckRequest">
   <part name="sku" type="xsd:string"/>
</message>

The return type of the method, AvailabilityType, reflects the message definition for PriceCheckResponse:

<message name="PriceCheckResponse">
   <part name="result" type="avail:availabilityType"/>
</message>

This is the first use for the AvailabilityType encoding type we discussed earlier.

The client application, then, can be coded to that interface. In the simple PriceCheckTest class, we see the invocation lines

PriceCheckService pcs = new PriceCheckService();
PriceCheckPortType pcpt = pcs.getPriceCheck();
AvailabilityType at = pcpt.checkPrice(args[0]);

where args[0] is a string (that should correspond to a valid stock keeping unit [SKU]) taken from the command line. In this case, the application uses the PriceCheckService to create an instance of the client stub populated with the endpoint address of the priceCheck Web service. (We'll discuss the client stub in more detail shortly.) The PriceCheckService factory could be used in many other ways—for example, to determine the endpoint address of the Web service implementation from a UDDI lookup. Regardless of the method used to derive a client stub, the portType remains the same, allowing the client application to rely on coding to that interface. The bindings might be different, either pointing to different Web services at different endpoint addresses, or perhaps even using different network transports, such as e-mail. Perhaps the Web service is a local method call within the same JVM. The client application is insulated from these changes because it is coding to an interface defined by a portType. This level of loose coupling between the requestor's application and the service provider makes Web services really great!

The complete PriceCheckTest class is presented in Listing 6.3.

Listing 6.3 The PriceCheckTest Class
package ch6.ex1;
import java.text.NumberFormat;

public class PriceCheckTest {
   public static void main(String[] args){
      if(args.length != 1){
         System.err.println("Usage: PriceCheckTest sku");
         System.exit(1);
      }
      try{
         PriceCheckService pcs = new PriceCheckService();
         PriceCheckPortType pcpt = pcs.getPriceCheck();
         AvailabilityType at = pcpt.checkPrice(args[0]);
         NumberFormat df = NumberFormat.getCurrencyInstance();

         System.out.println("One " + args[0] + " costs: " +
            df.format(at.getPrice()) + ".");
         System.out.println("There are " + at.getQuantityAvailable() + "
            available.");
      } catch (Exception e){
          System.err.println("Something wrong with the PriceCheck request");
          e.printStackTrace();
      }
   }
}

The PriceCheckService class, generated from the service element in the PriceCheck WSDL, is straightforward. The complete class is shown in Listing 6.4.

Listing 6.4 The PriceCheckService Class
package ch6.ex1;
public class PriceCheckService{

    // Use to get a proxy class for PriceCheck
    private final java.lang.String PriceCheck_address =
  "http://localhost:8080/axis/services/PriceCheck";
    public PriceCheckPortType getPriceCheck() {
       java.net.URL endpoint;
        try {
            endpoint = new java.net.URL(PriceCheck_address);
        }
        catch (java.net.MalformedURLException e) {
            return null; // unlikely as URL was validated in wsdl2java
        }
        return getPriceCheck(endpoint);
    }

    public PriceCheckPortType getPriceCheck(java.net.URL portAddress) {
        try {
            return new PriceCheckSOAPBindingStub(portAddress);
        }
        catch (org.apache.axis.SerializationException e) {
            return null; // ???
        }
    }
}

The name of the class is derived from the name of the service found in the priceCheck WSDL. The network address used

  private final java.lang.String PriceCheck_address =
"http://localhost:8080/axis/services/PriceCheck";

is derived from the soap:address element of the port:

<port name="PriceCheck" binding="pc:PriceCheckSOAPBinding">
   <soap:address
      location="http://localhost:8080/axis/services/PriceCheck"/>
</port>

The client application uses the client stub generated by Axis to invoke a Web service using a particular binding mechanism. The Axis WSDL tooling generates a client stub for each binding element found in the WSDL document. The client stub class has the same name as the binding element and implements the interface defined for the portType named in the binding element. A complete listing for the PriceCheckSOAPBindingStub appears in Listing 6.5.

Listing 6.5 The PriceCheckSOAPBindingStub Class
package ch6.ex1;
public class PriceCheckSOAPBindingStub extends org.apache.axis.wsdl.Stub
        implements PriceCheckPortType{
    private org.apache.axis.client.ServiceClient call =
        new org.apache.axis.client.ServiceClient(
            new org.apache.axis.transport.http.HTTPTransport());
    private java.util.Hashtable properties = new java.util.Hashtable();

    public PriceCheckSOAPBindingStub(java.net.URL endpointURL)
  throws org.apache.axis.SerializationException {
         this();
         call.set(org.apache.axis.transport.http.HTTPTransport.URL,
            endpointURL.toString());
    }
    public PriceCheckSOAPBindingStub()
        throws org.apache.axis.SerializationException {
        try {

            org.apache.axis.utils.QName qn1 =
                new org.apache.axis.utils.QName(
                    "http://www.skatestown.com/ns/availability",
              "AvailabilityType");
            Class cls = AvailabilityType.class;
            call.addSerializer(cls, qn1,
                new org.apache.axis.encoding.BeanSerializer(cls));
            call.addDeserializerFactory(qn1, cls,
         org.apache.axis.encoding.BeanSerializer.getFactory());
        }
        catch (Throwable t) {
            throw new org.apache.axis.SerializationException
                ("AvailabilityType", t);
        }

    }

    public void _setProperty(String name, Object value) {
        properties.put(name, value);
    }

    // From org.apache.axis.wsdl.Stub
    public Object _getProperty(String name) {
        return properties.get(name);
    }

    // From org.apache.axis.wsdl.Stub
    public void _setTargetEndpoint(java.net.URL address) {
        call.set(org.apache.axis.transport.http.HTTPTransport.URL,
            address.toString());
    }

    // From org.apache.axis.wsdl.Stub
    public java.net.URL _getTargetEndpoint() {
        try {
            return new java.net.URL((String)
         call.get(org.apache.axis.transport.http.HTTPTransport.URL));
        }
        catch (java.net.MalformedURLException mue) {
            return null; // ???
        }
    }

    // From org.apache.axis.wsdl.Stub
    public synchronized void setMaintainSession(boolean session) {
        call.setMaintainSession(session);
    }

    // From javax.naming.Referenceable
    public javax.naming.Reference getReference() {
        return null; // ???
    }

    public AvailabilityType checkPrice(String sku)
        throws java.rmi.RemoteException{
        if (call.get(org.apache.axis.transport.http.HTTPTransport.URL)
     == null){
            throw new org.apache.axis.NoEndPointException();
        }
        call.set(org.apache.axis.transport.http.HTTPTransport.ACTION, "");
        Object resp =
call.invoke("http://www.skatestown.com/services/PriceCheck",
            "checkPrice", new Object[]
                {new org.apache.axis.message.RPCParam("sku", sku)} );

        if (resp instanceof java.rmi.RemoteException) {
            throw (java.rmi.RemoteException)resp;
        }
        else {
             return (AvailabilityType) resp;
        }
    }

}

Most of the client stub code is generated to manipulate the ServiceClient, which, as you saw in Chapter 4, is the major interface between client applications and the Axis engine on the requestor side. Properties that are set include the transport and the network address of the service (given as a parameter, from the client application or the stub factory class). The client stub also configures the ServiceClient with serializers and deserializers for each complexType involved in the binding (that is, derived from the portType) using the encoding classes discussed earlier:

org.apache.axis.utils.QName qn1 = new org.apache.axis.utils.QName
   ("http://www.skatestown.com/ns/availability", "AvailabilityType");
Class cls = AvailabilityType.class;
call.addSerializer (cls, qn1,
   new org.apache.axis.encoding.BeanSerializer (cls));
call.addDeserializerFactory (qn1, cls,
   org.apache.axis.encoding.BeanSerializer.getFactory ());

These serializers and deserializers are very important because they are the part of the Axis engine that is responsible for converting something that looks like this

   <ns3:checkPriceResponse xmlns:ns3="http://www.skatestown.com/services/PriceCheck">
    <checkPriceResult href="#id0"/>
   </ns3:checkPriceResponse>
   <multiRef id="id0" xsi:type="ns5:AvailabilityType"
      xmlns:ns5="http://www.skatestown.com/ns/availability">
    <quantityAvailable xsi:type="xsd:int">36
    </quantityAvailable>
    <price xsi:type="xsd:double">129.0
    </price>
    <sku xsi:type="xsd:string">947-TI
    </sku>
   </multiRef>

into an instance of AvailabilityType, with the quantityAvailable, price, and sku instance variables properly set.

Finally, the core purpose of the client stub is to provide an implementation of each operation defined in the interface generated from the portType. In the PriceCheckSOAPBindingStub, there is one method implementation: checkPrice. This method is responsible for gathering the parameters, using the ServiceClient to invoke the Web service, and return the marshaled result back to the client application:

public AvailabilityType checkPrice(String sku)
    throws java.rmi.RemoteException{
    if (call.get(org.apache.axis.transport.http.HTTPTransport.URL) == null){
        throw new org.apache.axis.NoEndPointException();
    }
    call.set(org.apache.axis.transport.http.HTTPTransport.ACTION, "");
    Object resp = call.invoke("http://www.skatestown.com/services/
        PriceCheck", "checkPrice", new Object[]
            {new org.apache.axis.message.RPCParam("sku", sku)} );

    if (resp instanceof java.rmi.RemoteException) {
        throw (java.rmi.RemoteException)resp;
    }
    else {
         return (AvailabilityType) resp;
    }
}

And that is it for the Java code generated for use on the client side.

Generating the Server Skeleton and Interface

Generated code is also useful on the server side to insulate the implementation of the Web service from the details of SOAP, the network transport, and even Axis itself.

The server-side interface can take one of two forms, depending on whether the Web service implementation needs context information to be passed to it. Many Web service implementations are standalone; they need no information from the Axis engine relating to the context of the request (information about the requestor, transport specific information like the servlet context, other environment properties, and so on). In this case, the client-side interface based on portType is sufficient. However, for cases where context is required from the Axis engine, you should use the --messageContext option on the Wsdl2java, generating a server-side interface. The server-side interface generated from the priceCheck WSDL appears in Listing 6.6.

Listing 6.6 The PriceCheckPortTypeAxis Interface
package ch6.ex1;
public interface PriceCheckPortTypeAxis extends java.rmi.Remote {
    public AvailabilityType checkPrice(org.apache.axis.MessageContext ctx,
        String sku) throws java.rmi.RemoteException;
}

The server-side interface has the name of the portType appended with the string Axis; for example, PriceCheckPortTypeAxis.java. This interface is similar to the client-side interface, except that each method signature includes as its first parameter a MessageContext parameter, allowing the Web service implementation access to the environment through this object. Using MessageContext is a good way to encapsulate access to information such as the transport. The downside of doing this is that your Web service implementation is tied very closely to Axis. Al Rosen chose this approach for the priceCheck Web service because he used the same method to get at the ProductDatabase as the InventoryCheck Web service, and that method uses MessageContext.

The server-side skeleton is quite simple. One such class is defined for each binding element in the WSDL document, as shown in Listing 6.7.

Listing 6.7 The PriceCheckSOAPBindingSkeleton Interface
package ch6.ex1;
public class PriceCheckSOAPBindingSkeleton{
    private PriceCheckPortTypeAxis impl;

    public PriceCheckSOAPBindingSkeleton() {
        this.impl = new PriceCheckSOAPBindingImpl();
    }

    public PriceCheckSOAPBindingSkeleton(PriceCheckPortTypeAxis impl) {
        this.impl = impl;
    }
    public Object checkPrice(org.apache.axis.MessageContext ctx, String sku)
       throws java.rmi.RemoteException
    {
        Object ret = impl.checkPrice(ctx, sku);
        return ret;
    }

}

The skeleton defines a couple of simple constructors, allowing it to be associated with an implementation of the server-side interface—that is, an implementation of the Web service. In fact, the code is generated to look for one particular implementation class, named PriceCheckSOAPBindingImpl by default. A sample PriceCheckSOAPBindingImpl.java file is also generated, with trivial contents:

package ch6.ex1;

public class PriceCheckSOAPBindingImpl implements PriceCheckPortTypeAxis {
    public AvailabilityType checkPrice(org.apache.axis.MessageContext ctx,
  String sku)
        throws java.rmi.RemoteException {
        throw new java.rmi.RemoteException ("Not Yet Implemented");
    }
}

The skeleton generates an implementation method for each operation in the portType. Each operation is the same, invoking the corresponding method in the actual Web service implementation class.

The checkPrice implementation in the PriceCheckSOAPBindingSkeleton is quite simple:

public Object checkPrice(org.apache.axis.MessageContext ctx, String sku)
  throws java.rmi.RemoteException
{
    Object ret = impl.checkPrice(ctx, sku);
    return ret;
}

However, for Web services that involve in/out and out parameters, the implementation of the skeleton operation becomes much trickier.

In/out and Multiple Out Parameters

In certain RPC situations, WSDL can specify message parts that appear on both the input message and output message; these translate to in/out parameters. Further, WSDL can specify an output message with more than one part. In/out and multipart output messages pose an interesting problem for translating WSDL to Java.

Consider the following example WSDL:

<wsdl:message name="inmsg">
   <wsdl:part name="in1" type="in"/>
   <wsdl:part name="inout1" type="inout"/>
</wsdl:message>

<wsdl:message name="outmsg">
   <wsdl:part name="inout1" type="inout"/>
   <wsdl:part name="out1" type="out"/>
   <wsdl:part name="out2" type="out"/>
</wsdl:message>

<wsdl:portType name="pt1">
   <wsdl:operation name="op1">
      <wsdl:input message="inmsg"/>
      <wsdl:output message="outmsg"/>
   </wsdl:operation>
</wsdl:portType>

The operation named op1 has a message part named inout1 appearing in both the input message and output message. Further, you will notice that the output message of the operation has three output parts: the inout1 part, and two parts that appear only in the output message, out1 and out2.

How is it possible in Java to have a parameter that is both on the input and the output, or to have multiple output values? This is where the holder classes come in.

As we have seen, Axis WSDL tooling generates holder classes for each of the input, output, and in/out parameters. The classes generated when this WSDL file was processed included InHolder.Java, InOutHolder.Java, and OutHolder.Java. These holder classes are simple: They contain a value of the given underlying type and provide a simple constructor.

Now, the skeleton generated for the following binding

<wsdl:binding name="pt1SOAPBinding1" type="pt1">
   <soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
   <wsdl:operation name="op1">
      <soap:operation soapAction="op1"/>
      <wsdl:input>
         <soap:body use="encoded"
              namespace="someNamespace"
         encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
      </wsdl:input>
      <wsdl:output>
         <soap:body use="encoded"
              namespace="someNamespace"
        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
      </wsdl:output>
   </wsdl:operation>
</wsdl:binding>

looks like this:

public Object op1(org.apache.axis.MessageContext ctx, In in1, Inout inout1)
     throws java.rmi.RemoteException
    {
        InoutHolder inout1Holder = new InoutHolder(inout1);
        OutHolder out2Holder = new OutHolder();
        Object ret = impl.op1(ctx, in1, inout1Holder, out2Holder);
        org.apache.axis.server.ParamList list =
            new org.apache.axis.server.ParamList();
        list.add(new org.apache.axis.message.RPCParam("out1", ret));
        list.add(new org.apache.axis.message.RPCParam
            ("inout1", inout1Holder._value));
        list.add(new org.apache.axis.message.RPCParam
            ("out2", out2Holder._value));
        return list;
    }

Note the way the target Web service is dispatched. Holder objects are created for the in/out and the second out parameter and passed to the implementation. The implementation then puts a value in the Out2Holder object passed and potentially modifies the value in the InoutHolder that is passed. All three values are passed back to the requestor as RPCParams.

Consider the test program shown in Listing 6.8.

Listing 6.8 The In/Out and Out Parameter Test Program
package ch6.ex5;
public class Test {
   public static void main(String[] args){
      if(args.length != 2){
         System.err.println("Usage: Test int1 int2");
         System.err.println
            ("Multiplies int1 by int2 and returns that as out parm,");
         System.err.println("Adds value of int1 to int2 (as inout parm)");
         System.err.println
            ("Adds new value of inout parm with the result of the
   multiplication"
             + "to produce return value.");


         System.exit(1);
      }
      try{
         int int1 = Integer.valueOf(args[0]).intValue();
         int int2 = Integer.valueOf(args[1]).intValue();

         pt1 stub = new In_Out_Parm().getIn_Out_Test_Port();
         In in1 = new In(int1);
         InoutHolder inout1 = new InoutHolder(new Inout(int2));
         OutHolder out2 = new OutHolder();

             Out res = stub.op1 (in1, inout1, out2);

         System.out.println(int1 + "*" + int2 + " = " + out2._value.getE3());
         System.out.println(int1 + "+" + int2 + " = " + inout1._value.getE2());
         System.out.println(int1 + "*" + int2 + " + " + int1 + "+"
              + int2 + " = " + res.getE3());
      }catch (Exception e){
         System.err.println("Something wrong with the Test service");
         e.printStackTrace();
      }
   }
}

When this is run with the command

java ch6.ex5.Test 2 3

The following SOAP message is generated:

POST /axis/servlet/AxisServlet/In_Out_Test_Port HTTP/1.0
. . .
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
   xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

   <SOAP-ENV:Body>
      <ns3:op1 xmlns:ns3="someNamespace">
         <in1 href="#id0"/>
         <inout1 href="#id1"/>
      </ns3:op1>
      <multiRef id="id1" xsi:type="ns6:Inout"
         xmlns:ns6="anotherNamespace">
         <e2 xsi:type="xsd:int">3
         </e2>
      </multiRef>
      <multiRef id="id0" xsi:type="ns10:In" xmlns:ns10="anotherNamespace">
         <e1 xsi:type="xsd:int">2
         </e1>
      </multiRef>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

A sample Web service implementation of this service looks like Listing 6.9.

Listing 6.9 Example In/Out and Out Parameter Web Service Implementation
package ch6.ex5;

import java.rmi.RemoteException;

import org.apache.axis.MessageContext;

public class pt1SOAPBinding1Impl implements pt1Axis {

   /**
    * @see pt1Axis#op1(MessageContext, In, InoutHolder, OutHolder)
    * multiply the value of inout by in and toss it into out2
    * add in to inout and toss that into inout
    * return the total of inout and out2
    */
   public Out op1(MessageContext ctx, In in1, InoutHolder inout1,
                  OutHolder out2)
      throws RemoteException {
         out2._value = new Out();
         out2._value.setE3(in1.getE1()*inout1._value.getE2());
         inout1._value.setE2(inout1._value.getE2() + in1.getE1());
         return new Out(inout1._value.getE2() + out2._value.getE3());
   }

    /**
     * in case the messageContext parm was not used
     */
   public Out op1(In in1, InoutHolder inout1, OutHolder out2)
      throws RemoteException {
         out2._value = new Out();
         out2._value.setE3(in1.getE1()*inout1._value.getE2());
         inout1._value.setE2(inout1._value.getE2() + in1.getE1());
         return new Out(inout1._value.getE2() + out2._value.getE3());
   }
}

This is a simple program that takes the input value and increments the in/out value by that amount. Further, it returns in one output parameter the product of the in and the original in/out value, and in the "real" output of the message, returns the sum of the newly incremented in/out value and the other output parameter. It is a useless function, but it illustrates the in/out and multiple out situations in a simple fashion.

This program produces the following output SOAP response:

HTTP/1.0 200 OK. . .
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
   SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/
   xmlns:SOAP-ENV=http://schemas.xmlsoap.org/soap/envelope/
   xmlns:xsd=http://www.w3.org/2001/XMLSchema
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <SOAP-ENV:Body>
      <ns3:op1Response xmlns:ns3="someNamespace">
         <out1 href="#id0"/>
         <inout1 href="#id1"/>
         <out2 href="#id2"/>
      </ns3:op1Response>
      <multiRef id="id2" xsi:type="ns7:Out"
         xmlns:ns7="anotherNamespace">
         <e3 xsi:type="xsd:int">6
         </e3>
      </multiRef>
      <multiRef id="id1" xsi:type="ns11:Inout"
         xmlns:ns11="anotherNamespace">
         <e2 xsi:type="xsd:int">5
         </e2>
      </multiRef>
      <multiRef id="id0" xsi:type="ns15:Out"
         xmlns:ns15="anotherNamespace">
         <e3 xsi:type="xsd:int">11
         </e3>
      </multiRef>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Note the way the multiref element is used to contain the holder values back to the requestor. The test program shown in Listing 6.8 then takes these values from the returned holder classes and prints the following out on the screen:

2*3 = 6
2+3 = 5
2*3 + 2+3 = 11

This would have been extremely tricky to do manually, but it was nothing for the Axis WSDL tooling.

Now, back to our priceCheck example.

Generating the Deployment XML

In order for the Axis engine to know about a service, the service needs to be deployed. The current mechanism of deploying services in Axis is through a deployment descriptor, a deploy.xml file. Instructions on how to deploy a Web service are included in generated comments at the beginning of the deploy.xml file.

A deploy.xml file is generated to deploy all the Web services defined in a WSDL document. A service element is generated for each port element in the WSDL file. The deploy.xml file generated from priceCheck.wsdl is shown in Listing 6.10.

Listing 6.10 The deploy.xml File Generated from priceCheck.wsdl
<!--                                                             -->
<!--Use this file to deploy some handlers/chains and services  -->
<!--Two ways to do this:                                         -->
<!--  java org.apache.axis.utils.Admin deploy.xml              -->
<!--     from the same dir that the Axis engine runs             -->
<!--or                                                           -->
<!--  java org.apache.axis.client.AdminClient deploy.xml       -->
<!--     after the axis server is running                        -->
<!--This file will be replaced by WSDD once it's ready           -->

<m:deploy xmlns:m="AdminService">

   <!-- Services from PriceCheckService WSDL service -->
   <service name="PriceCheck" pivot="RPCDispatcher">
      <option name="className" value="PriceCheckSOAPBindingSkeleton"/>
      <option name="methodName" value=" checkPrice"/>
   </service>

   <beanMappings xmlns:avail="http://www.skatestown.com/ns/availability">
      <avail:AvailabilityType classname= "AvailabilityType"/>
   </beanMappings>
</m:deploy>

The value of the pivot handler is determined by the value of the style element of the soap:operation element; RPC generates RPCDispatcher, and document generates MsgDispatcher. An option element with the name className is generated for the binding referenced by the port. The actual value is set to the name of the skeleton Java file. The methodName option element contains a list of methods, one for each operation in the binding, each separated by a space. This deployment tells the Axis engine to invoke the PriceCheckSOAPBindingSkeleton class using the checkPrice operation. (Note that the deployment file has been modified to reflect the package names where the actual class files have been placed.)

The deploy.xml file also contains a collection of serializers associated with the Web service, associating an encoding class, in this case AvailabilityType.Java, with the corresponding type and namespace:

<beanMappings xmlns:avail="http://www.skatestown.com/ns/availability">
   <avail:AvailabilityType classname="AvailabilityType"/>
</beanMappings>

The Axis WSDL tooling also generates a document to undeploy the Web service. This document is called undeploy.xml. The undeploy contains a service element for each port in the WSDL document; this corresponds to the service elements generated in the deploy.xml file. The undeploy.xml file generated from priceCheck.wsdl is shown in Listing 6.11.

Listing 6.11 The undeploy.xml File Generated from priceCheck.wsdl
<!--                                                             -->
<!--Use this file to undeploy some handlers/chains and services  -->
<!--Two ways to do this:                                         -->
<!--  java org.apache.axis.utils.Admin undeploy.xml              -->
<!--     from the same dir that the Axis engine runs             -->
<!--or                                                           -->
<!--  java org.apache.axis.client.AdminClient undeploy.xml       -->
<!--     after the axis server is running                        -->
<!--This file will be replaced by WSDD once it's ready           -->
<m:undeploy xmlns:m="AdminService">

   <!-- Services from PriceCheckService WSDL service -->

   <service name="PriceCheck" pivot="RPCDispatcher">
   </service>
</m:undeploy>
Putting It All Together: A Running PriceCheck Service

The previous sections described the WSDL tooling in Axis, but what steps must Al Rosen take to get the priceCheck service running?

The first step, of course, is to acquire WSDL. Al Rosen created the priceCheck.wsdl file by hand. In the next section, we will discuss ways to generate WDSL from existing code, and in Chapter 7, we will see how to get a WSDL description from a services registry like UDDI.

Al then issued the Wsdl2java command shown previously to invoke the Axis WSDL tooling. This step generated the files we discussed earlier. Note that Al used the following options:

  • --skeleton, telling WSDL2java to generate server-side files (skeleton, deploy.xml, and undeploy.xml)

  • --messageContext, because Al knew the actual priceCheck Web service would use code from the InventoryCheck Web service that needed this parameter

  • --package, to add package declarations to each file

  • --output, to put the generated files in the proper directory

Next, Al created an implementation of a priceCheck service, PriceCheckSOAPBindingImpl, shown in Listing 6.12. This file completely replaces the generated PriceCheckSOAPBindingImpl.java file. This is the name of the class the PriceCheckSOAPBindingSkeleton is coded to invoke.

Listing 6.12 An Implementation of the PriceCheck Web Service
package ch6.ex1;
import org.apache.axis.MessageContext;
import bws.BookUtil;
import com.skatestown.data.Product;
import com.skatestown.backend.ProductDB;

/**
 * PriceCheck Web service, based on Inventory Web Service
 * Created by implementing a PriceCheckSOAPBindingImpl
 */
public class PriceCheckSOAPBindingImpl implements PriceCheckPortTypeAxis,
   PriceCheckPortType {
   /**
    * Checks price and quantity available given a product SKU
    *
    * @param msgContext  This is the Axis message processing context
    *                    BookUtil needs this to extract deployment
    *                    information to load the product database.
    * @param sku         product SKU
    * @return            A structure indicating price and quantity for the sku
    * @exception Exception most likely a problem accessing the DB
    */
    public AvailabilityType checkPrice (MessageContext msgContext,String sku)
         throws java.rmi.RemoteException{
      ProductDB db = null;
      try{
         db = BookUtil.getProductDB(msgContext);
      }catch (Exception e){
         throw new java.rmi.RemoteException(e.getMessage());
      }
      Product prod = db.getBySKU(sku);
      if(prod == null){
         throw new
            java.rmi.RemoteException("Product sku: " + sku + " not found.");
      }

      AvailabilityType at = new AvailabilityType();
      at.setSku(sku);
      at.setQuantityAvailable(prod.getNumInStock());
      at.setPrice(prod.getUnitPrice());
      return at;
   }

    public AvailabilityType checkPrice (String sku)
         throws java.rmi.RemoteException{
       throw new java.rmi.RemoteException(
         "Need to generate WSDL with -messageContext option!");
    }
}

This implementation is based on the InventoryCheck Web service you saw in Chapter 3. The input parameter sku is used to retrieve the product record from the product database. From this information, an Availability object is created, filled in with values, and returned to the requestor.

The next step is to deploy the Web service to a server. In this case, Al moved the .class files to a webApps directory under his Web application server and started the server. Al then updated the deploy.xml file to make sure the proper path structure was reflected in the className option element and the beanMappings elements. (Note that this step was required even though Al used the -–package option.) Al then invoked the AdminClient, following the instructions in the comments found in the deploy.xml file, deploying the Web service to Axis. The priceCheck Web service is now available to requestors.

The PriceCheckTest client application (shown in Listing 6.3) simply takes a sku from the command line and uses the generated client-side stub to invoke for the priceCheck Web service. The results of the priceCheck are then printed to the console.

For example, if Al ran the command

java ch6.ex1.PriceCheckTest 947-TI

the output could look like this:

One 947-TI costs: $129.00.
There are 36 available.

That's it for generating Java and XML from WSDL. The point here is that tooling allowed Al Rosen to focus on the business application and not worry about the intricacies of SOAP and Axis. Al focused on creating the WSDL description for the Web service, the actual business logic implementing the Web service, and a simple test client. The Axis WSDL tooling generated the rest based on the WSDL description of the Web service.

Summarizing WSDL to Java

Table 6.3 summarizes the parts of a WSDL document that map to different pieces of the generated Java and deployment XML.

Table 6.3. How WSDL Elements Map to Generated Java and Deployment XML
WSDL Elements Generated Java/XML Example
<types>    
each complexType
  • Generate a serializer/deserializer class

  • AvailabilityType.java

 
  • Generate a holder class

  • Configure the serviceClient in the client stub with serializers/deserializers

  • Generate a BeanMapping in the deploy.xml file

  • AvailabilityType Holder.java

<portType>
  • Generate a client-side interface class

  • PriceCheckPortType.java

 
  • Generate a server-side interface class

  • PriceCheckPortTypeAxis. java

each operation
  • Becomes a method signature in the interface

  • AvailabilityType checkPrice(String sku)...

each input part
  • Becomes an input parameter to the interface method

  • AvailabilityType checkPrice(String sku)...

each output part
  • First part becomes the return type of the method

 
 
  • Subsequent parts become method parameters represented by holder classes for the type defined by the part

  • AvailabilityType checkPrice(String sku)...

each fault element
  • Generates a class defining a Java exception of the same name

  • Does not appear in PriceCheck

 
  • Generates a Java exception added to the method signature

 
<binding>
  • Generates a client-side stub

  • Generates a server-side skeleton

  • PriceCheckSOAPBinding Stub.java

 
  • Generates a pair of option elements (className and methodName) in the deploy.xml file

  • PriceCheckSOAPBinding Skeleton.java

  • PriceCheckSOAPBinding Impl.java

each soap:operation
  • Sets the SOAPAction property of the ServiceClient in the client stub from the value of the soapAction attribute

  • call.set(HTTPTransport. ACTION, " ");

 
  • Sets the handler in deploy.xml based on the value of soap:style

  • Generates an implementation method in the client-side stub

  • Generates a skeleton method in the server-side skeleton

  • Generates an entry in the deploy.xml methodList

 
<port>
  • Sets the endpointURL in the service class from the soap:address element

  • Generates one service element in deploy.xml

 
  • Generates one service elemen in undeploy.xml

 
<service>
  • Generates one service class

  • PriceCheckService.java

 
  • Generates one deploy.xml file

  • Generates one undeploy.xml file

 

Deriving WSDL from Code

We mentioned in the previous section that Al Rosen needed to manually create the priceCheck WSDL definition. For applications that already exist, there are alternative approaches to coming up with the WSDL definition. Axis WSDL tooling can generate a WSDL document from a Java class.

Axis makes available the WSDL for any deployed service. You can examine the WSDL by invoking the service using HTTP get (from a Web browser) with the ?WSDL parameter. Figure 6.5 shows the WSDL generated by Axis for the InventoryCheck service.

Figure 6.5. Generating the WSDL for the InventoryCheck Web service.

graphics/06fig05.gif

The WSDL for the service is generated by visiting all the chains and handlers deployed for the service, including service-specific chains, transport-specific chains, and global chains. Doing so ensures that each component of the deployment is incorporated into the WSDL for the Web service.

The core WSDL is generated based on the Java class definition, defining the operations and messages that are core to any WSDL description. Other details, such as the SOAPAction value, are also determined by the deployment.

Much work continues in the area of generating WSDL from Java and other programming language artifacts, such as EJBs, database schemas, and so on. This is another area of Web services that is seeing a lot of effort in standardization and tooling. We will see some examples of other Web services environments and tooling in Chapter 8.

    Previous Section Next Section