Chapter 4. SOAP-RPC, SOAP-Faults, and Misunderstandings

SOAP-RPC

SOAP-RPC defines a model for representing an RPC and an RPC response using the SOAP infrastructure. It is not necessarily bound tightly to a synchronous request/reply model, or to the HTTP protocol. In fact, both the SOAP 1.1 and 1.2 specifications explicitly state that the use of SOAP-RPC is orthogonal to the protocol binding. The specifications do concede that when SOAP-RPC is bound to HTTP, an RPC invocation maps naturally to an HTTP request, and an RPC return maps naturally to an HTTP response, but this natural mapping is purely coincidental. One of the goals of the SOAP 1.2 effort was to distance itself from the point of view that SOAP is inherently an RPC mechanism. As a result, SOAP-RPC was moved into the optional “Adjuncts” portion of the specification.

That said, what’s really important is that SOAP defines a uniform model for representing an RPC and its return value or values. The fundamental requirements for an RPC call are that the body element contains the method name and the parameters and that the parameters are accessible via accessors.[5] In addition, SOAP has provisions for encoding the method signature, header data, and the URI that represents the destination.

In the next example, we’ll look at a SOAP-RPC client that calls a remote service that returns the value of a book at Barnes & Noble. The service is hosted and available at http://www.xmethods.net. Let’s start by running the client and examining its output:

java GetBookPrice

The default settings look up the price of O’Reilly’s Java Message Service, using its ISBN number.

You should see the following output:

_________________________________________________________
Starting GetBookPrice:
    service url     = http://services.xmethods.com:80/soap/servlet/rpcrouter
    ISBN#       = 0596000685
_________________________________________________________

The price for O'Reilly's The Java Message Service book is 34.95

Congratulations! You have just executed a SOAP-RPC invocation over the Internet and received a response with a return value. Let’s examine the SOAP messages that just went over the wire and the code that made it happen. To see what the SOAP looks like, we can reroute the transaction to our SimpleHTTPReceiver with this command:

java GetBookPrice -url http://localhost:8080/examples/servlet/SimpleHTTPReceive 
    -isbn 0596000686

You will see the following output in the Tomcat servlet window (we have reformatted some of the output for readability):

____________________________
Received request.
-----------------------
  SOAPAction = ""
  Host = localhost
  Content-Type = text/xml; charset=utf-8
  Content-Length = 461
-----------------------
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
    <ns1:getPrice xmlns:ns1="urn:xmethods-BNPriceCheck" 
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <isbn xsi:type="xsd:string">0596000686</isbn>
    </ns1:getPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
____________________________

When using SOAP-RPC, the body of the envelope contains the method name and the parameters for the procedure call. In this SOAP message, <ns1:getPrice> is an automatically generated tag that represents the method name. The parameter <isbn> is represented by the type xsd:string and has a value of 0596000686.

The SOAP-Encoding Attribute

SOAP encoding is a set of rules that designates how datatypes are encoded, or serialized, over the wire. In this message, the encodingStyle attribute is set to the value http://schemas.xmlsoap.org/soap/encoding/. This particular URL defines the encoding rules based on the schema for SOAP 1.1. If you look at that URL directly, you’ll see that it is an actual XML Schema document. Among other things, it defines the xsd:string type used for the <isbn> tag. If this SOAP call used SOAP 1.2, the encodingStyle attribute would be set to http://www.w3.org/2001/09/soap-encoding.

The SOAP encoding covers rules for serializing any datatype, ranging from simple scalar types such as int, float, and string, to complex datatypes such as structures, arrays, and sparse arrays.[6]

SOAP-RPC Method Signatures

The rules for method signatures simply state that the <body> element contains a single SOAP struct. The elements in the struct each have to be referenceable by an accessor. In SOAP, an element with an accessor can be identified directly by a named tag (for example, <isbn>) or by an ordinal value (as in an array value). If multiple parameters exist, they must appear in the same order as they appear in the signature of the receiving method. Finally, the types have to match. If this example used a second parameter, such as a book title, the body might look like this:[7]

<SOAP-ENV:Body>
    <ns1:getPrice xmlns:ns1="urn:xmethods-BNPriceCheck" 
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
        <isbn xsi:type="xsd:string">0596000686</isbn>
        <title xsi:type="xsd:string">Java And Web Services</isbn>
    </ns1:getPrice>
</SOAP-ENV:Body>

The rules for the response are similar. The response is also a named struct that can contain multiple values. In the SOAP document itself, no direct correlation exists between the request and the response. By convention, the name of the return value should be the same as the name of the request method with the string “Response” appended; for example, getPriceResponse would be the return value for getPrice. However, this role is only a convention, not a requirement. Furthermore, the association between the format of the request and the format of the response is also arbitrary; there is no way to dictate in the request which parameters are [in] parameters and which are [in|out] parameters. As we will see, this deficiency is addressed by WSDL. A WSDL definition can include the complete XML Schema for the request and the response.

SOAP 1.2 imposes two additional rules: the name of the return value accessor is result, and it is namespace-qualified with the namespace identifier http://www.w3.org/2001/09/soap-rpc.

The SOAP-RPC Sender—Remote Service

Here’s a complete listing of the SOAP-RPC client that we used to look up a book price on Barnes & Noble:

import java.io.*;
import java.util.*;

public class GetBookPrice {

  // default values to be used if not supplied on the command line
  private static final String DEFAULT_SERVICE_URL =
    "http://services.xmethods.com:80/soap/servlet/rpcrouter";
  private static final String DEFAULT_BOOK_ISBN = "0596000685";
  private String m_serviceURL;
  private String m_bookISBN;

  public GetBookPrice (String serviceURL, String bookISBN) throws Exception
  {
        //this section displays the status of the call to the service
        m_serviceURL = serviceURL;
        m_bookISBN   = bookISBN;

        System.out.println(  );
        System.out.println(
            "______________________________________________________");
        System.out.println("Starting GetBookPrice:");
        System.out.println("    service url     = " + m_serviceURL);
        System.out.println("    ISBN#           = " + m_bookISBN);
        System.out.println(
            "_______________________________________________________");
        System.out.println(  );
  }

  public static float sendSoapRPCMessage (String url, String isbn) 
                            throws Exception 
  {

    //Build the call.
    org.apache.soap.rpc.Call call = new org.apache.soap.rpc.Call (  );

    //This service uses standard SOAP encoding
    String encodingStyleURI = org.apache.soap.Constants.NS_URI_SOAP_ENC;
    call.setEncodingStyleURI(encodingStyleURI);

    //Set the target URI
    call.setTargetObjectURI ("urn:xmethods-BNPriceCheck");

    //Set the method name to invoke
    call.setMethodName ("getPrice");

    //Create the parameter objects
    Vector params = new Vector (  );
    params.addElement (new org.apache.soap.rpc.Parameter("isbn", 
                            String.class, isbn, null));

    //Set the parameters
    call.setParams (params);

    //Invoke the service
    org.apache.soap.rpc.Response resp = call.invoke (new java.net.URL(url),"");

    //Check the response
    if (resp.generatedFault (  )) {
       org.apache.soap.Fault fault = resp.getFault(  );
       System.err.println("Generated fault: ");
       System.out.println("  Fault Code   = " + fault.getFaultCode(  ));
       System.out.println("  Fault String = " + fault.getFaultString(  ));
       return 0;
    } else {
       org.apache.soap.rpc.Parameter result = resp.getReturnValue (  );
       Float FL = (Float) result.getValue(  );
       return FL.floatValue(  );
    }
  }

    public static void main(String args[]) {

        // Argument parsing stuff
        ...

        try
        {
            GetBookPrice soapClient = new GetBookPrice(serviceURL, bookISBN);

            // call method that will perform RPC call using supplied Service
            // url and the book ISBN number to query on
            float f = soapClient.sendSoapRPCMessage(serviceURL, bookISBN);

            // output results of RPC service call
            if (bookISBN != DEFAULT_BOOK_ISBN) {
              System.out.println(
                "The Barnes & Noble price for this book is " + f);
            }else {
              System.out.println(
                "The price for O'Reilly's The Java Message Service book is " + f);
            }

        } catch(Exception e) {
            System.out.println(e.getMessage(  ));
        }
    }
...
}

Let’s examine the code in detail, paying particular attention to the pieces that do the real work. The code starts with several import statements, which import some standard Java packages plus some Apache packages we used for handling the SOAP messages. After the import statements, the class declaration, and some field declarations, we have the constructor—which sets some default parameters and does some runtime reporting:

import java.io.*;
import java.util.*;

public class GetBookPrice {

  // default values to be used if not supplied on the command line
  private static final String DEFAULT_SERVICE_URL =
                   "http://services.xmethods.com:80/soap/servlet/rpcrouter";
  private static final String DEFAULT_BOOK_ISBN = "0596000685";
  private String m_serviceURL;
  private String m_bookISBN;

  public GetBookPrice (String serviceURL, String bookISBN) throws Exception
  {
       ...
  }

The real workhorse of this application is sendSoapRPCMessage( ) . This method builds the SOAP Call object and populates it with the information necessary for remote service. This client calls the Barnes & Noble service and provides it with an ISBN number for a book. The service returns the retail value for that book. In Apache SOAP, the key to making this work is specifying the correct target URI for the call object that the service uses to identify itself. The service creator specifies this value in a deployment descriptor when it registers the service. setMethodName( ) sets the name of the method you want to call; this method must exist in the service referenced in the URN. Finally, the client creates parameter objects for the call. The number of parameters and their types must match the parameters that the service expects:

  public static float sendSoapRPCMessage (String url, String isbn) 
                            throws Exception 
  {

    //Build the call.
    org.apache.soap.rpc.Call call = new org.apache.soap.rpc.Call (  );

    //This service uses standard SOAP encoding
    String encodingStyleURI = org.apache.soap.Constants.NS_URI_SOAP_ENC;
    call.setEncodingStyleURI(encodingStyleURI);

    //Set the target URI
    call.setTargetObjectURI ("urn:xmethods-BNPriceCheck");

    //Set the method name to invoke
    call.setMethodName ("getPrice");

    //Create the parameter objects
    Vector params = new Vector (  );
    params.addElement (new org.apache.soap.rpc.Parameter("isbn", 
                            String.class, isbn, null));

    //Set the parameters
    call.setParams (params);

The service is then invoked by calling the Call object’s invoke( ) method, passing the URL of the service and the SOAP action, if more than one exists. This RPC call is synchronous, meaning that invoke( ) blocks until a response is returned. When the response is received, sendSoapRPCMessage( ) first checks whether the call returned a SOAP fault. (More information on SOAP faults can be found in Section 4.2 of this chapter.) If the call was successful, it extracts the return value from the response object and displays it:

    //Invoke the service
    org.apache.soap.rpc.Response resp = call.invoke (new java.net.URL(url),"");

    //Check the response
    if (resp.generatedFault (  )) {
       org.apache.soap.Fault fault = resp.getFault(  );
       System.err.println("Generated fault: ");
       System.out.println("  Fault Code   = " + fault.getFaultCode(  ));
       System.out.println("  Fault String = " + fault.getFaultString(  ));
       return 0;
    } else {
       org.apache.soap.rpc.Parameter result = resp.getReturnValue (  );
       Float FL = (Float) result.getValue(  );
       return FL.floatValue(  );
    }
}

Another SOAP-RPC Sender: Local Service

That example was cool. Let’s look at how to do the server portion. Unfortunately, we don’t have access to the Barnes & Noble’s server, so we have to run another RPC example on a local machine so we can watch what’s going on. In this example, we’ll look at a SOAP-RPC client that calls a local service we deploy using a Tomcat server. The service accepts an item number and returns the stock on hand for that item. Here’s a listing of the client, CheckStock:

import java.net.*;
import java.util.*;

public class CheckStock {

    private static final String DEFAULT_HOST_URL 
        = "http://localhost:8080/soap/servlet/rpcrouter";
    private static final String DEFAULT_ITEM = "Test";
    private static final String URI = "urn:oreilly-jaws-samples";

    //Member variables
    private String m_hostURL;
    private String m_item;

    public CheckStock (String hostURL, String item) throws Exception
    {
        m_hostURL = hostURL;
        m_item    = item;
        // print stuff to the console
        ...
    }

    public void checkStock(  ) throws Exception {

      //Build the call.
      org.apache.soap.rpc.Call call = new org.apache.soap.rpc.Call (  );

      //This service uses standard SOAP encoding
      String encodingStyleURI = org.apache.soap.Constants.NS_URI_SOAP_ENC;
      call.setEncodingStyleURI(encodingStyleURI);

      //Set the target URI
      call.setTargetObjectURI ("urn:stock-onhand");

      //Set the method name to invoke
      call.setMethodName ("getQty");

      //Create the parameter objects
      Vector params = new Vector (  );
      params.addElement (new org.apache.soap.rpc.Parameter("item", 
                            String.class, m_item, null));

      //Set the parameters
      call.setParams (params);

      //Invoke the service
      org.apache.soap.rpc.Response resp 
            = call.invoke ( new java.net.URL(m_hostURL),"urn:go-do-this");

      //Check the response
      if (resp != null) {
         if (resp.generatedFault (  )) {
            org.apache.soap.Fault fault = resp.getFault (  );
            System.out.println ("Call failed due to a SOAP Fault: ");
            System.out.println ("  Fault Code   = " + fault.getFaultCode (  ));
            System.out.println ("  Fault String = " + fault.getFaultString (  ));
         } else {
            org.apache.soap.rpc.Parameter result = resp.getReturnValue (  );
            Integer intresult = (Integer) result.getValue(  );
            System.out.println ("The stock-on-hand quantity for this item is: " 
                + intresult );
         }
      }
    }

    /** Main program entry point. */

    public static void main(String args[]) {
        // Command line parsing
        ...

        // Start the CheckStock client
        try
        {
            CheckStock stockClient = new CheckStock(hostURL, item);
            stockClient.checkStock(  );
        }catch(Exception e){
            System.out.println(e.getMessage(  ));
        }
    }
 ...
}

This client is similar to our previous client for looking up book prices, GetBookPrice. Thus, rather than reviewing how to write a simple client, we’ll concentrate on the service side of the application.

The SOAP-RPC Service

Here’s a listing of the SOAP-RPC service:

import java.net.*;
import java.io.*;
import java.util.*;

public class StockQuantity{

  public int getQty (String item) 
    throws org.apache.soap.SOAPException {

    int inStockQty = (int)(java.lang.Math.random(  ) * (double)1000);
    
    return inStockQty;
  }
  // main(  ) for command line testing
  ...
}

This program is obviously very simple and doesn’t require much explanation. However, you should note the class name and method name. These names are important when you register the service with the server, and in turn when you call the service from the sender. In our case, the class name (StockQuantity) and method name (getQty) are used in the Deployment Descriptor, which describes the service to the Apache SOAP server.

The Deployment Descriptor

Here is a listing of the Deployment Descriptor used to register the service with the server. Use the appropriate server manager to register the Deployment Descriptor:

<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
             id="urn:stock-onhand">
  <isd:provider type="java"
                scope="Application"
                methods="getQty">
    <isd:java class="StockQuantity"/>
  </isd:provider>
  <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
</isd:service>

The Deployment Descriptor contains information that the server needs to call the service successfully, such as the service and provider elements. The service element contains the namespace with which this descriptor is associated (i.e., the namespace that defines the tags used within the descriptor) and the URN used by the service to identify itself to the server. The caller uses this value in the setTargetObjectURI( ) method of the Call object.

The provider element contains information about the type of service, the scope of the service (i.e., Request, Session, or Application), and the methods exposed by the service. In our case, the service is clearly a Java service; we specify Application scope, which means that a single instance of the service class is created when the server starts; and we have only one service exposed and accessible, getQty. Finally, we specify the class name associated with the service. Class location needs to be accessible to the server.



[5] Accessors as defined in SOAP-encoding. They can be referenced by either a tag or an ordinal value such as an array index.

[6] A subset of an array, for which only the “sparsely populated” portions of the array are relevant.

[7] If you actually try to add a second parameter with the existing service, you may learn about handling SOAP Faults sooner than you think.

Get Java Web Services now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.