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
.
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]
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
.
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( ); } }
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.
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.
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.