Chapter 4. RPC-Style Services
Although SOAP is not limited to a particular style of distributed computing, it lends itself to a remote procedure call (RPC) model. This is only what you would expect; according to the SOAP specification, one of SOAP’s design goals is to encapsulate and exchange RPC calls. This approach maps very nicely to Java programming because method calls on Java objects can be easily translated to RPC calls. We’ll start this chapter by looking at the structure of SOAP RPC request and response messages. From there we’ll implement some RPC services in Java and deploy those services in both Apache SOAP and in GLUE. And, of course, we’ll write some Java code to make use of those services.
SOAP RPC Elements
Creating a SOAP RPC request uses the SOAP structure and encoding described in Chapter 2 and Chapter 3. No new XML or data encoding styles are needed for RPC. Let’s take a look at what’s required to represent an RPC method call in SOAP:
The target object
Method name
Method parameters
SOAP header data
Target Object URI
The target object URI is, essentially, the resource address of the service that we want to use. You see resource addresses all the time when you browse the Web — you navigate to a web page by specifying the page’s resource address. For instance, to look at the page describing the author of O’Reilly’s JavaBeans book, you’d use the URL http://www.oreilly.com/catalog/javabeans/author.html. The protocol, server address, and resource are all together. Let’s break it down so you can see the parts. The server address is www.oreilly.com, the HTML page resource is /catalog/javabeans/author.html, and the protocol is http.
This is exactly what we do to specify the target object in a SOAP RPC
request message, with one difference: instead of an HTML page
resource, the target object name is used. The target object name is
carried by the transport (for example, by an HTTP header), not the
SOAP message itself. This is the only part of a SOAP RPC message that
is carried outside the message except for the
SOAPAction
attribute. In an Apache SOAP server,
the target object for an RPC message is usually
/soap/rpcrouter
. This URI represents the resource
that routes SOAP RPC messages in Apache SOAP. For a GLUE server, the
target resource is usually /glue
, followed by the
name of the service itself. We’ll look at the HTTP
headers for both of these servers a little later.
Method Name
The
method name is, as you might
have guessed, the name of the method to be invoked. Back in Chapter 2 we talked about a service that provided the
current temperature at a weather station. That service exposed a
method called getCurrentTemperature
. If you like
to think in Java terms, you can think of the method name as a public
method on an instance of a service object.
In the SOAP XML, the method being
invoked is packaged as a SOAP struct[1] that contains an accessor for each parameter. The method
name struct
is an immediate child of the SOAP
Body
element. Let’s look again at
the getCurrentTemperature
example:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:GetCurrentTemperature xmlns:m="WeatherStation"> <m:scale>Celsius</m:scale> </m:GetCurrentTemperature> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Notice that the name of the method being invoked is
namespace-qualified using the name of
the service object. The namespace is supposed to be a URI that
provides the target service with a mechanism to interpret the meaning
of the method. The URI would normally point to a resource that
describes the method, but in this example this will work just fine.
But what service object are we talking about? It’s
not the target object. It’s another object that is
known to the target object by a particular name. In this case, the
service object’s name is
WeatherStation
. The mechanism used to map the
names of service objects to their physical implementations is not
specified by the SOAP specification. Each SOAP implementation has its
own way of doing that, which we’ll see in Section 4.3.
The method signature may also be included as part of the method struct, but it isn’t required to be there. In fact, the SOAP specification doesn’t describe the format of the method signature, and signatures won’t be used in this book (and aren’t used by the servers I discuss). I mention method signatures only so you won’t be confused if you run into an implementation that uses them.[2]
Method Parameters
The
parameters to the method are modeled
as accessors and are immediate child elements of the method struct.
This is true for all in
and
in/out
parameters; any out
parameters will not appear in the method invocation struct. SOAP
allows for the processing of methods with missing parameters, as we
saw in Chapter 3. It’s up to the
application to decide whether or not to process a call with missing
parameters. If leaving out parameters is not acceptable, the
application should respond with a SOAP fault. Each accessor is named
after the parameter name for the enclosing method, and its type is
the same as the corresponding service method type.
The previous SOAP envelope has one parameter to the
getCurrentTemperature
method. The parameter is
called scale
, and is used to specify the
temperature scale that should be used to return the result. This
parameter is namespace-qualified using the service name, which is not
unlike using a schema name to qualify the accessor. This means that
the scale
element is presented according to its
definition in the WeatherStation
namespace. We
could have chosen to include an xsi:type
attribute
on scale
, but omitting the attribute is valid as
well. As long as the type matches the type that’s
specified in the schema referred to by the namespace, you
don’t have to specify the type in the SOAP message.
You can even send a different type, as long as you specify what the
type is.
Let’s look at some Java
code and see how it maps to a SOAP RPC method call. The
DowJones30
class represents the current levels of
the Dow Jones industrial average. DowJones30
contains a method called getCurrentLevel( )
that
returns the Dow Jones industrial average for the current trading
session. It also has a method called getPrice( )
that returns the current trading price for a member stock of the Dow
Jones Industrial Average. getPrice( )
takes a
string parameter called stock
that represents the
stock symbol, and a currency
string parameter that
specifies the currency that should be used when returning the price.
The price is returned as a float
.
public class DowJones30 { private float _level; public float getCurrentLevel( ) { return _level; } public float getPrice(String stock, String currency) { float result; // determine the price for stock and return it // in the specified currency return result; } }
Now
let’s say we’ve already deployed a
service named DowJones
that is implemented by the
DowJones30
class. To invoke the
getCurrentLevel( )
method of the
DowJones
service, we use a SOAP envelope that
looks like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:getCurrentLevel xmlns:m="DowJones"> </m:getCurrentLevel> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
There are no parameters, since the getCurrentLevel(
)
method does not take any parameters. Now
let’s call the getPrice( )
method, using the stock symbol IBM
and a currency
of USD
(U.S. dollars) as parameters.
Here’s what the envelope would look like:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:getPrice xmlns:m="DowJones"> <m:stock>IBM</m:stock> <m:currency>USD</m:currency> </m:getPrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Method Response
A
method response also takes the form
of a SOAP struct. The method struct
contains an accessor for the return value, followed by accessors for
each out
and in/out
parameter
value in the same order they appear in the method
signature,[3] if one exists. The names of the
accessors for
the out
and in/out
values
correspond to their names in the method signature, but the names of
the return values don’t matter. My preference is to
give the return value a name that reflects the method name itself. I
tend to name service methods using a verb-noun style, which then
allows me to name the return value accessor with the noun. For
instance, if my service name is computeSize
, then
I name the return value accessor size
.
It’s become common practice to name the return value
struct by appending the word Response
to the name
of the method that was invoked. So for a method called
computeSize
, the struct containing the result
would be named computeSizeResponse
. Although this
is a common convention, it is not required, and
you’ll find that some SOAP implementations
don’t follow it.
Here’s the SOAP envelope that might be returned by
invoking the getCurrentLevel
method:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:getCurrentLevelResponse xmlns:m="DowJones"> <m:currentLevel>10513.15</m:currentLevel> </m:getCurrentLevelResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
Fault Response
If a
SOAP RPC method call fails, a SOAP
fault should be returned. Let’s take a look at an
example of a SOAP fault. Say we call the getPrice
method of the DowJones
service but specify an
invalid currency parameter. The service might return a fault that
looks like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>SOAP-ENV:Client</faultcode> <faultstring>Client Error</faultstring> <detail> <m:dowJonesfaultdetails xmlns:m="DowJones"> <message>Invalid Currency</message> <errorcode>1234</errorcode> </m:dowJonesfaultdetails> </detail> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
A SOAP RPC method call either fails or succeeds, but never both. So you’ll never find a method response and a fault response together.
Optional Header Data
Processing an RPC sometimes requires information that is not part of the method signature. For instance, it may be necessary to pass a username to a service. A username is usually not appropriate as a parameter to the method call, but nonetheless is needed by the receiving application. Information like this can be encoded in the SOAP header, as described in Chapter 2. There can also be transport-specific data; for example, you may want to pass cookie information as part of your HTTP header in order to specify the HTTP session to use for this method invocation. In either case, this additional data is encoded outside the bounds of the SOAP body.[4]
Service Activation
The lifetime of an object is an important part of any distributed system, and is sometimes referred to as service scope or service activation. Whatever terminology is used, it encompasses the various possible object lifetime models available in the system. In SOAP systems based on HTTP, there are generally three service activation models:
- Request-level service activation
For each request made on a given service object, a new instance of the object is created that persists only until the request is complete. This kind of service activation is useful for service objects that don’t maintain state.
- Application-level service activation
A single instance of the service object handles every method invocation for the entire lifetime of the service application. This kind of activation can be used for services that maintain state or that place a large burden on resources during object creation.
- Session-level service activation
A single instance of the service object is used for all method invocations for the lifetime of the session. Sessions are not necessarily a standard, as you may know. It’s possible to implement sessions specific to your own applications by doing things like generating and passing tokens, or you can take advantage of existing approaches like the use of cookies. This is certainly one of those cases where you need to pass additional header information.
The SOAP specification doesn’t define any requirements or behaviors for service object lifetime; there are no real requirements for SOAP implementations to support the three models described here. However, these activation models are found outside of SOAP as well, and you can expect to find them in many if not all of the SOAP implementations available. Each implementation may have its own unique way of setting the activation model for a service, but the behavior should be the same. You’ll probably find that the service activation model for a service is specified as part of the service deployment. We’ll cover this topic in practice when we talk about service deployment later in this chapter.
A Simple Service
Let’s design a
simple service called
CallCounterService
. This service keeps track of the
number of method calls it receives. It’s not exactly
useful by itself, but it gives us a chance to get a service up and
running to see how services are deployed and how the service
activation models work. We’ll place the classes for
this example in the package
javasoap.book.ch4
. Make sure
to create the appropriate directory for this package on your system
and make it available on the classpath used by your server and client
systems.
Here’s the
source code for the MethodCounter
class, which
implements the CallCounterService
. Note that the
Java class name does not have to be the same as the service name.
package javasoap.book.ch4; public class MethodCounter { int _counter; public MethodCounter( ) { _counter = 0; } public int getCount( ) { return _counter; } public boolean doSomething( ) { _counter += 1; return true; } }
As you can see, there is nothing special about the Java code.
MethodCounter
contains a variable called
_counter
that keeps track of the number of method
calls made during the lifetime of the object. The class contains a
method called doSomething( )
that increments the
_counter
variable and returns a Boolean value
indicating whether the call was successful. There’s
also a method called getCount( )
that returns the
number of times doSomething( )
has been called. We
named the method getCount( )
according to the
JavaBeans naming conventions for a
read-only integer property named Count
. We obey
the JavaBeans conventions for a few reasons. First, it makes sense to
follow standard conventions whenever we can. More important,
we’ll see later that some SOAP implementations can
make use of JavaBeans properties without requiring the programmer to
do extra work.
Note that our service class uses
a no-argument constructor. This is important. Java SOAP
implementations load service classes dynamically, usually by calling
Class.newInstance(
)
.
This method requires the class to have a constructor with no
arguments. Of course, if we didn’t explicitly
include a constructor, a default no-argument constructor would have
been created automatically. We include the constructor explicitly to
highlight the fact that it’s needed, regardless of
how it is included.
Before we go any further,
let’s take a look at the SOAP envelope we would use
to encapsulate the invocation of the doSomething(
)
method:
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <m:doSomething xmlns:m="CallCounterService"> </m:doSomething> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
The first child element of the SOAP Body
is the
method being invoked; the element is namespace-qualified using the
service name (not the name of the class that implements the service).
There are no parameters for the doSomething
element, since there are no parameters to the doSomething(
)
method of our service class.
Deploying the Service
Service deployment is not part of the SOAP specification, so each implementation has its own deployment procedure. We’ll look at service deployment using two SOAP implementations: Apache SOAP and GLUE.
Deploying with Apache SOAP
In
Apache SOAP, you must create a deployment
descriptor, which is an
XML file that contains information
about the service and the Java class that implements the service.
Let’s take a look at a
deployment descriptor for
CallCounterService
, implemented by the Java class
javasoap.book.ch4.MethodCounter
:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:CallCounterService"> <isd:provider type="java" scope="Application" methods="doSomething getCount"> <isd:java class="javasoap.book.ch4.MethodCounter" static="false"/> </isd:provider> <isd:faultListener> org.apache.soap.server.DOMFaultListener </isd:faultListener> <isd:mappings> </isd:mappings> </isd:service>
The outermost
XML element is called service
,
which is
namespace-qualified
using the identifier isd
, representing the
http://xml.apache.org/xml-soap/deployment
namespace. The service
element also has an
id
attribute that is assigned the unique name of
the service being deployed. I’ve prefixed the
service name, CallCounterService
, with
urn
. You don’t need to do this,
but it’s a common practice that
I’ll follow, at least in this example. Just be aware
that it’s not a requirement.
The first child element is called
provider
, which describes the implementation
of the service. As with all the child elements of
service
, this element is also namespace-qualified
with the isd
identifier from the parent element.
Three attributes are used on the
provider
element. The first attribute is
type
, which
tells Apache SOAP what implementation type this service is using; we
assign it the value java
. (Apache SOAP allows for
service implementations other than Java, but we
won’t be covering them.) The
scope
attribute specifies the service
activation model of the deployed service.
Possible values for this attribute are
Application
, Session
, and
Request
. These correspond to the activation models
described earlier.
We’re using
Application
level service activation now; we will
experiment with other values later. The third attribute of the
provider
element is called
methods
. This
attribute lists the method names that are exposed by the service.
These method names have to match the
corresponding methods in the Java class that implements the service
because Apache SOAP uses dynamic class loading and Java reflection in
order to make method calls. The method names are separated by spaces.
For this example we’re exposing the
doSomething
and getCount
methods.
The
provider
contains a child element that further
describes the details of the service implementation. We assigned the
type
attribute a value of java
,
which corresponds to the child element name java
.
The java
element has two attributes and no data.
The class
attribute is assigned the full name of
the Java class that implements the service, in this case
MethodCounter
. An attribute called
static
indicates whether the exposed methods of
the service implementation class are static.
The
next element of the deployment descriptor is a child element of
service
called faultListener
.
The data value of this element is the full name of the Java class
used to create a fault listener object. This object is responsible
for building SOAP faults to be returned when a fault condition
exists.
We’re using the
org.apache.soap.server.DOMFaultListener
class,
which is provided by Apache SOAP. In Chapter 7
we’ll look more closely at SOAP faults and write our
own fault listener.
The last element is called
mappings
. In Chapter 6
we’ll use this section to define Java classes that
manage the serialization and deserialization of custom Java types.
For now we don’t need any mappings, so there are no
entries in the mappings section.
Next, we need to use the deployment descriptor to deploy our service.
I’ve named the deployment descriptor file
CallCounterService.dd; it
doesn’t matter what name you use, as long as it
makes sense and is recognizable to you. At this point, you should
start your server, if it isn’t running already.
Apache SOAP includes a Java
application that you can use from a command line to deploy a service.
This application is called the service manager client
(org.apache.soap.server.ServiceManagerClient
); it
can deploy, undeploy, and list services. Here’s what
the command to deploy the service looks like:
java org.apache.soap.server.ServiceManagerClient http://georgetown:8080/soap/servlet/rpcrouter deploy CallCounterService.dd
The first parameter is the URL of the
Apache SOAP RPC router; I’m
running an Apache Tomcat server with Apache SOAP that accepts
connections on port 8080. The server is a machine on the local
network called georgetown
. The second parameter,
deploy
, tells the service manager client that we
are deploying a service. The last parameter,
CallCounterService.dd
, is the name of the file
that contains the deployment descriptor. If all goes well, you
won’t see any output from the service manager
client; you’ll just return to the command prompt. If
you get any exceptions or other error messages, look carefully at the
output. Some likely problems are an incorrect classpath, or running
the command from a directory other than the one that contains the
deployment descriptor file.
After you’ve successfully deployed the service, you can verify it by calling the service manager client again. This time we’ll ask the service manager to list all of the deployed services:
java org.apache.soap.server.ServiceManagerClient http://georgetown:8080/soap/servlet/rpcrouter list
The first parameter is the same as before: the URL of the Apache SOAP
RPC router. list
tells the service manager to list
the currently deployed services. You should see the following
response:
Deployed Services: urn:CallCounterService
Apache SOAP persists the information for the deployed services, so if you shut down the server that is hosting Apache SOAP and restart it at a later time, the services will still be available. It doesn’t, however, persist the state of the objects that implement those services; they will start as if for the first time, with a new lifetime.
The Apache SOAP Admin tool
You
might find it easier to use the web-based interface for Apache SOAP
administration. The URL for this interface starts with the address
and port of the server hosting Apache SOAP, followed by the SOAP
Admin resource. I’m running Apache SOAP on
georgetown
on port 8080, so the URL is
http://georgetown:8080/soap/admin/index.html.
Figure 4-1 shows the main Apache SOAP
administration page.
The List button takes you to a page that lists all of the deployed
services. You’ll see the
urn:CallCounterService
listed, as shown in Figure 4-2. The urn:CallCounterService
link takes you to a page that shows the details of our service
deployment, as shown in Figure 4-3. You can see all
the deployment details that we specified in the deployment
descriptor.
You can also deploy and undeploy services via this interface. If you
click on Un-deploy, you’ll find a list of deployed
services. To undeploy a service, just click on its link. If you like,
go ahead and undeploy the urn:CallCounterService
.
You can also use the service manager client from the command line to
undeploy a service:
java org.apache.soap.server.ServiceManagerClient http://georgetown:8080/soap/servlet/rpcrouter undeploy urn:CallCounterService
Now that the service is no longer deployed, click the Deploy button on the web interface. Figure 4-4 shows the form for service deployment. The information for deploying the service is already filled in.
To deploy the service, scroll down to the bottom of the form, where you’ll find a button labeled Deploy. Click that button and the service will be deployed. You can use the web interface to look at the deployed service to make sure it looks the same as before. Apache SOAP makes no attempt to validate any of the information, so make sure you get it right.
There’s an obvious security issue here. Would you want to allow anyone with the right URL to deploy or undeploy services? Not likely. Use the security features of the hosting technology in a production environment.
Deploying with GLUE
We
can use the same Java class to
implement CallCounterService
in GLUE, because like
Apache SOAP, GLUE does not require any special or extra code for
services. However, the process of deploying GLUE services is quite
different from that of Apache SOAP, and quite a bit simpler. GLUE
provides a couple of ways to deploy services. We’ll
deploy the service by writing a standalone application that starts an
instance of GLUE’s integrated HTTP server within our
own code, and then use the GLUE classes and related methods to
publish the service. Here’s the application:
package javasoap.book.ch4; import electric.util.Context; import electric.registry.Registry; import electric.server.http.HTTP; public class CallCounterApp { public static void main( String[] args ) throws Exception { HTTP.startup("http://georgetown:8004/glue"); Context context = new Context( ); context.addProperty("activation", "application"); Registry.publish( "urn:CallCounterService", javasoap.book.ch4.MethodCounter.class, context ); } }
The
CallCounterApp
class contains the
required static main( )
method so it can be called
from the command line. The HTTP.startup(
)
method starts an instance of an HTTP
server on my local machine georgetown
on port
8004, and the resource used to accept and route SOAP messages is
/glue
. Putting that together gives us the full URL
of http://georgetown:8004/glue, which is passed
as a parameter to HTTP.startup( )
.
After starting the server, we need to register our service with it.
We start by specifying the activation model we want to use. To do so,
we create an instance of the class
electric.util.Context
.
This class is part of the GLUE APIs,
and is used to assemble a collection of property names and associated
values. To record the activation model, we call the
addProperty( )
method of the
context
object, giving it the property name
activation
and the associated value
application
. Finally, we’re ready
to publish the service, using the publish(
)
method of GLUE’s
electric.registry.Registry
class. The first
parameter passed is urn:CallCounterClass
, which
once again is the name of the service being published. The second
parameter is the Java class that implements the service,
MethodCounter
. The context
object is passed as the last parameter, indicating that we want to
publish (deploy) the service using the application-level service
activation model.
To start the application, we enter the following from a command-line prompt:
java javasoap.book.ch4.CallCounterApp
When we start the application, we’ll see some output
telling us the GLUE version number as well as the full URL of the
GLUE resource that is listening for SOAP messages. This is the same
URL that was passed to the HTTP.startup( )
method.
At this point the
urn:CallCounterService
service is deployed and
ready to accept SOAP RPC method invocations.
How does GLUE know what methods are being
exposed as part of the service? In the Apache SOAP deployment
descriptor we specifically defined the methods that were exposed; we
haven’t done anything like that for GLUE. Instead,
GLUE uses a number of techniques to determine the methods
automatically. Therefore, the simplest way to deploy a service is to
let GLUE figure out which methods to expose on its own. In later
chapters we’ll look at how to gain control over the
methods that are exposed. For this example, GLUE exposes all the
public methods of the MethodCounter
class except
for the constructor. You shouldn’t be surprised that
GLUE requires a no-argument constructor, just like Apache SOAP.
The GLUE console
GLUE includes
a web-based interface that allows you to see the available services
on a given server, edit them, and even deploy new services. The GLUE
console is a special web application that can be started using the
console.bat (or console.sh)
script that is included in the /bin directory of
the GLUE installation. If you’re using a
command-line prompt in a Microsoft Windows environment, I suggest you navigate
to that directory to execute the console
command,
because console
is a Windows command as well. If
you navigate to GLUE’s /bin
directory first, you’ll start the correct program.
By default, the GLUE console
listens for HTTP connections to the local machine on port 8100 (i.e.,
http://localhost:8100). To run the console
application on another port, specify that port number as an argument
to the console
command. The following command
starts the console on port 8150:
console 8150
All the examples in this book use the default port of 8100. So go ahead and start the console application, then launch your web browser and go to http://localhost:8100. You will see the main GLUE console window shown in Figure 4-5.
For now, we’re going to use only the first field of
the GLUE console interface, labeled HOME. Remember that we have
already started an instance of a GLUE server using the URL
http://georgetown:8004/glue. Enter this URL in
the HOME field of the console form, click on the HOME button, and
you’ll be taken to the page shown in Figure 4-6. This page includes the URL of the
server’s endpoint, the resource that manages the
SOAP messages. It also shows all of the services currently available
on that server. The first one,
system/admin
, is a special service used by
GLUE. The second service is
urn:CallCounterService
, which we deployed earlier.
The urn:CallCounterService
link takes you to a
page that describes the web service, shown in Figure 4-7.
Let’s take a look at the
information provided for a web service. The Description field
provides a description of the service; its value is a default value
that GLUE has generated for us. In later examples
we’ll provide a more useful description. The
Endpoint field contains the full URL of the service.
The WSDL
field shows a URL that can be used to request a description of the
service in the Web Service Description Language;
we’ll look at this in Chapter 9.
The Class field shows the full class name of the Java class that
implements the service, and the Mode field shows that the service
activation model used by this service is
application
. Finally, the Methods field contains
the methods that can be invoked on this service. These were
automatically determined by GLUE.
The EDIT button takes you to a form that allows you to edit the
description, Java class, and mode (service activation model) of the
service. It also allows you to modify the URN (universal
resource name). The URN is the actual name of the service, which in
this case is urn:CallCounterService
.
Clicking the JAVA
button gives you a listing of client-side Java classes that can be
used to access the service. We’ll get to this in Section 4.4.2.
You can undeploy (delete) the service by clicking the DEL button. If we go ahead and delete the service, the GLUE console responds with the page shown in Figure 4-8.
At this point, click the “here”
link to go back to the page that describes the available web services
on the server. This will look similar to Figure 4-6
except that urn:CallCounterService
will no longer
be listed. Now click the ADD button, which takes you to a form for
providing the information needed to deploy a service (Figure 4-9). I’ve entered the
information for the urn:CallCounterService
, this
time providing a better description. I used the same URN, class, and
mode used when we deployed the service from within the Java
application.
Press the SAVE button to deploy the service. If you provide an invalid class name, the GLUE console will indicate a failure. Otherwise you’ll be taken to the web service page (Figure 4-7).
Writing Service Clients
In the next few sections, we’ll write some Java code that invokes methods on the services we’ve exposed in both Apache SOAP and GLUE. For now, we won’t mix and match technologies — we’ll use Apache SOAP APIs to call an Apache SOAP server, and GLUE APIs to call a GLUE server.
So what about interoperability? One of the most important aspects of SOAP as a wire protocol is that your choice of implementation should not prohibit you from communicating successfully with other SOAP implementations. However, there are still some problems with SOAP interoperability. Some technologies are better at it right now than others, and in some cases you have to jump through a few hoops to make different SOAP technologies communicate properly with each other. So let’s put off this subject until Chapter 9, where we’ll cover these issues in detail.
Calling the Service with Apache SOAP
Now
that we have a service up and running,
let’s write some Java code that acts as a client of
the service. Here’s a simple Java application that
invokes the getCount
method of the
urn:CallCounterService
on our Apache SOAP server:
package javasoap.book.ch4; import java.net.*; import org.apache.soap.*; import org.apache.soap.rpc.*; public class GetCountApp { public static void main(String[] args) throws Exception { URL url = new URL( "http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call( ); call.setTargetObjectURI("urn:CallCounterService"); call.setMethodName("getCount"); try { Response resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue( ); Object value = ret.getValue( ); System.out.println("Result is " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode( ) + "): " + e.getMessage( )); } } }
This program starts by creating an
instance of java.net.URL
using the URL of the
Apache SOAP RPC router of our Apache SOAP server. It then creates an
instance of org.apache.soap.rpc.Call
, a Java class
that encapsulates a SOAP RPC
method call. The setTargetObjectURI(
)
method of the call
object is used to specify the name of the service, and
setMethodName(
)
specifies the name of the method being
invoked.
At this point we’re ready to invoke the method on
the specified service. Since the method invocation can throw a
SOAPException
, we put it within a
try/catch
block. If anything goes wrong
we’ll print out the fault code and message from the
exception.
To make the method call, we use the invoke(
)
method of the call
object, with the URL that we set up earlier as the first parameter.
The second parameter is a SOAP action URI, which is included as an
HTTP header entry. We’re not using a
SOAP action, so an empty string is passed for this parameter. The
invoke( )
method returns an instance of
org.apache.soap.rpc.Response
; upon successful
return, this object contains the response from the server.
For now let’s assume that
the call succeeds. After invoking the method, we call the
getReturnValue( )
method of the response object.
This method returns an instance of
org.apache.soap.rpc.Parameter
, which contains the
return value, or response, of the service method call. Now we call
the getValue( )
method of the parameter, which
returns an instance of Object
; the actual type of
the object you get back depends on the return type of the method.
This is an important point. Apache
SOAP RPC method calls return instances of
Java objects, not
instances of Java primitives. In a case like this, where the service
method being called returns an int
value, the
primitive return value is created as an instance of its corresponding
Java object type — in this case, Integer
.
We could
cast the return value to Integer
, but instead
we’ll leave it as an Object
instance and print the result using System.out.println(
)
.
This
method implicitly calls toString( )
, which returns
the Integer
’s value as a
String
.
Let’s go ahead and run this application:
java javasoap.book.ch4.GetCountApp
If everything is set up correctly, you’ll see the following output:
Result is 0
The result is 0 because we haven’t yet made any
calls to the doSomething( )
method that increments
the value of the counter in the service object. This is great!
We’ve successfully deployed and invoked a SOAP
service in Java. Let’s take a look at the SOAP
payload that was used to invoke the service:
POST /soap/servlet/rpcrouter HTTP/1.0 Host: Georgetown Content-Type: text/xml; charset=utf-8 Content-Length: 344 SOAPAction: "" <?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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getCount xmlns:ns1="urn:CallCounterService"> </ns1:getCount> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
All of the data in this SOAP document should be familiar to you. The
information specific to this example is contained in the SOAP body,
where you can see that the getCount
element (which
represents the method being invoked) is namespace-qualified by
urn:CallCounterService
, the name of the service on
which the method is being invoked. While we’re at
it, let’s look at the response sent back by the
server:
HTTP/1.0 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: 468 Set-Cookie2: JSESSIONID=yqf5f6su31;Version=1;Discard;Path="/soap" Set-Cookie: JSESSIONID=yqf5f6su31;Path=/soap Servlet-Engine: Tomcat Web Server/3.2.3 <?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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getCountResponse xmlns:ns1="urn:CallCounterService" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="xsd:int">0</return> </ns1:getCountResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
The
HTTP
headers are shown so
that you can see the entire response. Notice that the server is
returning HTTP cookie values. Although we aren’t
using them now, those cookies will be useful when we use
Session
service activation for services.
Now look at the SOAP body. Since the name of the method that was
invoked is getCount
, the first child element of
the body is getCountResponse
, namespace-qualified
by the service name. The
SOAP-ENV:encodingStyle
attribute is set to
http://schemas.xmlsoap.org/soap/encoding
. Within
the getCountResponse
element we find a single
child element named return
, which contains the
return value of the getCount
method.
return
is explicitly typed as
xsd:int
, and contains the data value
0
.
Let’s make a few calls to
doSomething
in order to bump up the counter:
package javasoap.book.ch4; import java.net.*; import org.apache.soap.*; import org.apache.soap.rpc.*; public class GetCountApp2 { public static void main(String[] args) throws Exception { URL url = new URL( "http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call( ); call.setTargetObjectURI("urn:CallCounterService"); try { call.setMethodName("doSomething"); Response resp = call.invoke(url, ""); resp = call.invoke(url, ""); resp = call.invoke(url, ""); call.setMethodName("getCount"); resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue( ); Object value = ret.getValue( ); System.out.println("Result is " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode( ) + "): " + e.getMessage( )); } } }
In this example, we first set the name of the method to
doSomething
using setMethodName(
)
, and then we execute call.invoke( )
three times. Then the method name is set to
getCount
and the service is invoked again. Like
last time, the result of the getCount
method is
displayed. You should see the following output after running this
application from a command-line prompt:
Result is 3
We’ve deployed
urn:CallCounterService
using application scope,
meaning that a single instance of the service class is used for every
service request. So each time the doSomething
method is called, the _counter
variable inside the
MethodCounter
instance is incremented. If you run
the application again, the result will be 6. One more time and
it’ll be 9. Here you see application scope services
in action, trivial as the example itself happens to be. If you want
to get the counter back to 0, shut down the server
you’re using to host Apache SOAP and restart
it.
Tip
You’d think that an alternative to shutting down the server would be to undeploy and then redeploy the service. It doesn’t work, though, at least not with a Tomcat server. Apparently the implementing service class is cached and reused if the service is redeployed. If you’re using some other server to host Apache SOAP, you should check this out for yourself.
Calling the Service with GLUE
Now
let’s
create an application that uses the GLUE APIs to communicate with the
GLUE server we started earlier. GLUE takes a different approach to
Java client programming. It allows you to access services via local
Java interfaces. These interfaces contain methods that correspond to
the exposed, or published, methods of the service. In this example,
we want to write a Java interface that has the getCount(
)
and doSomething( )
methods just as
they appear in MethodCounter
.
Here’s such an interface, named
ICallCounterService
.[5]
package javasoap.book.ch4; public interface ICallCounterService { int getCount( ); boolean doSomething( ); }
Next let’s create an application that accesses the
GLUE-hosted service urn:CallCounterService
.
package javasoap.book.ch4; import electric.registry.Registry; import electric.registry.RegistryException; public class GetCountApp3 { public static void main(String[] args) throws Exception { try { ICallCounterService ctr = (ICallCounterService)Registry.bind( "http://georgetown:8004/glue/urn:CallCounterService.wsdl", ICallCounterService.class); System.out.println("Result is " + ctr.getCount( )); } catch (RegistryException e) { System.out.println(e); } } }
GLUE allows us to bind the service
interface to the service. Once this is accomplished, we simply use
the interface as if it were a local object (which it is!). So the
only step is to call the static bind( )
method of
the electric.registry.Registry
class.
The first parameter is a
URL to the WSDL file
that describes the service. GLUE is based on WSDL, and automatically
generates the WSDL description for the service. (WSDL is covered
briefly in Chapter 9.) To create the proper URL,
start with the address of the server and resource
(http://georgetown:8004/glue) and add the name
of the service, followed by .wsdl. The second
parameter is the Java class that represents the interface,
ICallCounterService.class
. The return value of the
Registry.bind( )
method is cast to an instance of
the Java interface for the service,
ICallCounterService
.
After the call to Registry.bind( )
returns, we can
use the ctr
variable to call the
getCount( )
and doSomething( )
methods as if it were nothing more than a local Java object. In this
example we simply print out the result by calling
ctr.getCount( )
. Here’s how to
run the example:
java javasoap.book.ch4.GetCountApp3
The result of calling getCount( )
is 0 because
there have not yet been any calls to doSomething(
)
:
Result is 0
The GLUE console
can generate some Java code that makes it easier to access the
service. To generate this code, go back to the GLUE console using
your browser, then go to the
urn:CallCounterService
and click on the JAVA
button. GLUE uses a different name for the interface
(IMethodCounter
) than I did, but the contents of
the interface are otherwise the same.
The helper class is called
MethodCounterHelper
. It encapsulates the
Registry.bind( )
calls and the cast required to
pass the correct interface. The helper class provides two static
bind( )
methods. The first one takes no parameters
and generates the URL of the service endpoint automatically. The
other bind( )
allows you to specify another URL if
you like. Here’s the previous client application
modified to use the Java interface and helper class generated by the
console. Make sure that you use the appropriate package name for the
interface and helper class. The GLUE console does not do that part,
so it’s up to you.[6]
package javasoap.book.ch4; import electric.registry.RegistryException; public class GetCountApp4 { public static void main(String[] args) throws Exception { try { IMethodCounter ctr = MethodCounterHelper.bind( ); System.out.println("Result is " + ctr.getCount( )); } catch (RegistryException e) { System.out.println(e); } } }
Using the helper class MethodCounterHelper
cleans
up the code a bit. For a small program like this,
it’s not a significant difference, but there would
be a bigger payoff in a larger application. Compile the new client,
together with the Java interface and helper class as part of the
javasoap.book.ch4
package. Running this example
from a command-line prompt gives us the same result as before.
The GLUE console also provides a way to invoke the methods of a
service without writing any code. Go back to the
urn:CallCounterService
web service page in your
browser. The methods that are listed in the METHODS field are
hyperlinks; clicking on one of these links brings you to a form for
invoking the selected service method. If you select the
getCount
method, you’ll see the
form shown in Figure 4-10. There are fields for the
input parameters and return value, labeled INPUTS and OUTPUTS. If
there were any parameters for the getCount
method,
there would be a place to enter a value. In this case there are none.
Clicking on the SEND button invokes the service method. When the call
returns, the result is displayed below the SEND button; if you do
this now, you’ll see a 0. This web interface
provides a useful tool for checking the functionality of web services
without having to write Java code.
Let’s modify the GLUE example to make multiple calls
to doSomething
before calling
getCount
. Here’s a new version of
our client that uses the GLUE APIs to make multiple calls, using the
interface and helper class generated by the GLUE console:
package javasoap.book.ch4; import electric.registry.RegistryException; public class DoSomethingApp { public static void main(String[] args) throws Exception { try { IMethodCounter ctr = MethodCounterHelper.bind( ); ctr.doSomething( ); ctr.doSomething( ); ctr.doSomething( ); System.out.println("Result is " + ctr.getCount( )); } catch (RegistryException e) { System.out.println(e); } } }
When you run this client for the first time, you get the following output:
Result is 3
If you run it again, the result will be 6, and so on, because the
service is using application scope (activation mode). To reset the
counter to 0, stop and then restart the
CallCounterApp
application, or use the GLUE
console to delete the service and then add it back. With the GLUE
console, the latter approach
works just fine.
Deploying with Request-Level Scope
Up until now, all the examples we’ve created have been deployed using application-level scope (service activation). In this mode, a single instance of the implementing service class handles all of the method invocations. Let’s change the deployment to request-level scope. With request-level scope, a new instance of the service object is created for each method invocation, and that object is destroyed when the invocation is complete.
First we’ll change the example running under
Apache SOAP by modifying its
deployment descriptor. The only change required is to set the
scope
attribute of the
provider
element to Request
;
the rest of the deployment descriptor remains exactly the same:
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:CallCounterService"> <isd:provider type="java" scope="Request" methods="doSomething getCount"> <isd:java class="javasoap.book.ch4.MethodCounter" static="false"/> </isd:provider> <isd:faultListener> org.apache.soap.server.DOMFaultListener </isd:faultListener> <isd:mappings> </isd:mappings> </isd:service>
Remember to undeploy and then redeploy the
urn:CallCounterService
using any of the techniques
discussed earlier. You don’t have to shut down the
server the way we did before; now that we’re using
request-level scope, there shouldn’t be any instance
of MethodCounter
in memory anyway. So you can use
the command-line version of the service
manager client to undeploy and redeploy the service, or you can use
the Apache SOAP Admin browser application to do the same.
If you decide to shut down
the server and restart it, you’ll still have to
undeploy and redeploy the service because Apache SOAP maintains
deployed services persistently.
Now that you’ve redeployed
urn:CallCounterService
using request-level scope,
you can use the Apache version of the client application to access
the service. Even though the application calls doSomething(
)
multiple times before calling getCount(
)
, the result will always be 0. This is a direct
consequence of request-level scope: whenever either of these methods
is invoked, a new instance of the MethodCounter
class is created, and that instance is destroyed when the invocation
is complete. No state is maintained across invocations. Therefore,
there’s no way for the count to be anything but 0.
Let’s do the same for the service running under
GLUE. The easiest way to accomplish this is to use the GLUE console.
Browse to the web services page for the
urn:CallCounterService
service, and then click the
EDIT button. This takes you to the web service editor page shown in
Figure 4-11.
To change the activation mode, click the corresponding radio button in the MODE field and select Request. Now click the SAVE button, and the service is redeployed using request-level scope.
Tip
I’ve been using different terms for the service activation model. Sometimes I call it scope, sometimes activation mode, and other times service activation model. They all mean the same thing, but different implementations use different terms and I want you to get used to all of them. The words may be different, but they all still refer to the lifetime of the object that implements the service.
You can check that the service is now using request-level scope by
running the DoSomethingApp
application again. You
will always get a result of 0. Again, the calls to
doSomething
and the call to
getCount
are all made against separate instances
of the
MethodCounter
class.
Deploying with Session-Level Scope
Apache SOAP provides for
session management by passing
cookies via the
HTTP headers. Earlier in this
chapter, we saw an HTTP response from the server that included HTTP
header entries called Set-Cookie
and
Set-Cookie2
. The client application uses these
cookies if it needs to make subsequent method invocations against the
same session as the original request.
The Apache
SOAP API uses a simple technique for handling this.
org.apache.soap.transport.http.SOAPHTTPConnection
includes a method called setMaintainSession( )
that takes a single boolean
parameter. This
parameter, when set to true
, tells the connection
object to maintain the current session. The connection object
implements this by keeping track of the cookies and sending them back
to the server when the next method invocation takes place.
Go ahead and edit the deployment descriptor, setting the
scope
attribute to Session
. Now
run the client program again. You’ll get the
following output:
Result is 3
This output proves that the same instance of the service object was
used for all of the method invocations. If you run it again
you’ll get the same result because a new instance of
the session object is created on the first call to
doSomething( )
, and each subsequent method call
uses the same service object. Because we’re using
the same instance of org.apache.soap.rpc.Call
for
every service call, we’re also using the same
instance of
org.apache.soap.transport.http.SOAPHTTPConnection
.
You’ve probably guessed that the default behavior of
org.apache.soap.transport.http.SOAPHTTPConnection
is to maintain the session.
A
common use for session-level scope is a shopping cart, where you want
to keep track of the session between service method invocations.
However, there may be times when you do not want to use the same
session for all of your method calls, even though the service is
deployed using session scope. In the next example, the
SOAPHTTPConnection
object is retrieved from the
Call
object after the first method invocation.
Then a call to the connection’s
setMaintainSession( )
method is made with a
parameter of false
. This stops the connection from
using the same session on subsequent service method calls.
package javasoap.book.ch4; import java.net.*; import org.apache.soap.*; import org.apache.soap.rpc.*; import org.apache.soap.transport.http.*; public class SessionApp { public static void main(String[] args) throws Exception { URL url = new URL( "http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call( ); call.setTargetObjectURI("urn:CallCounterService"); try { call.setMethodName("doSomething"); Response resp = call.invoke(url, ""); SOAPHTTPConnection st = (SOAPHTTPConnection)call.getSOAPTransport( ); st.setMaintainSession(false); resp = call.invoke(url, ""); st.setMaintainSession(true); resp = call.invoke(url, ""); call.setMethodName("getCount"); resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue( ); Object value = ret.getValue( ); System.out.println("Result is " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode( ) + "): " + e.getMessage( )); } } }
Before the third call to doSomething( )
, we call
setMaintainSession( )
again with a parameter of
true
. Therefore, after the third call to
doSomething( )
, the
SOAPHTTPConnection
object will keep track of the
session cookies. Then, when we call getCount( )
,
those cookies will be sent back to the server, ensuring that the same
session object is used again. You should get the following output
when you run the SessionApp
application:
Result is 1
If you want to maintain a
single session across multiple calls, make sure that you establish
the session before the first call that should be part of that
session. However, programming this way is a little sloppy, at least
for my taste. I would prefer to create a new instance of the
SOAPHTTPConnection
and use it when I begin making
calls using a single session. Of course, these considerations apply
only to services deployed using session scope. If
you’d like to give this a try, change the code
within the try
block to look like this:
call.setMethodName("doSomething"); Response resp = call.invoke(url, ""); resp = call.invoke(url, ""); SOAPHTTPConnection st = new SOAPHTTPConnection( ); call.setSOAPTransport(st); resp = call.invoke(url, ""); call.setMethodName("getCount"); resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue( ); Object value = ret.getValue( ); System.out.println("Result is " + value);
The lifetime of a server session is
implementation dependent. The server platform you are using, the way
it’s configured, and the available configuration
options all play a part in how long a session lasts. In most cases,
the server terminates a session after some period of idle time has
passed. GLUE
manages sessions automatically, and lets
you set the session lifetime by calling
electric.server.http.HTTP.setSessionTimeout( )
.
The parameter passed to this method is an integer that contains the
number of seconds that the session can remain inactive before it is
invalidated.
Passing Parameters
So far, the examples in this chapter
have used methods that don’t contain any parameters.
We’ll now spend a little time looking at how
parameters are passed. Let’s create a service called
urn:StockPriceService
, implemented by the
javasoap.book.ch4.StockPrice
class. The getPrice(
)
method takes parameters for
the stock symbol as well as the currency. The method always returns
the float value of 75.33 — not a very interesting stock, but at
least it won’t go down. Seriously though,
we’re just interested in how parameters are passed.
The details of accessing a database or data feed to find a stock
price are, for the moment, left to you.
package javasoap.book.ch4; public class StockPrice { public float getPrice(String stock, String currency) { float result; // determine the price for stock and return it // in the specified currency result = (float)75.33; return result; } }
We’ll gloss
over the deployment details, both for Apache SOAP and GLUE, since
we’ve spent much of this chapter on that subject.
Here’s the Apache
SOAP deployment descriptor for the
StockPriceService
implemented by the
StockPrice
class. The service uses
application-level scope, and
contains a single method called getPrice
that
retrieves the current trading price of a stock using a specified
currency.
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:StockPriceService"> <isd:provider type="java" scope="Application" methods="getPrice"> <isd:java class="javasoap.book.ch4.StockPrice" static="false"/> </isd:provider> <isd:faultListener> org.apache.soap.server.DOMFaultListener </isd:faultListener> <isd:mappings> </isd:mappings> </isd:service>
This service can be deployed in
Apache SOAP using the browser-based Admin tool or by putting this
deployment descriptor into a file and using the service manager
client. We’ll use the latter approach. The file
containing the deployment descriptor is
StockPriceService.dd. Using that file, the
following command will deploy the service on my local machine
georgetown
:
java org.apache.soap.server.ServiceManagerClient http://georgetown:8080/soap/servlet/rpcrouter deploy StockPriceService.dd
Here is a simple application using the
Apache SOAP APIs to access the
StockPriceService
:
package javasoap.book.ch4; import java.net.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; public class StockPriceApp { public static void main(String[] args) throws Exception { URL url = new URL( "http://georgetown:8080/soap/servlet/rpcrouter"); Call call = new Call( ); call.setTargetObjectURI("urn:StockPriceService"); call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC); String stock = "MINDSTRM"; String currency = "USD"; Vector params = new Vector( ); params.addElement(new Parameter("stock", String.class, stock, null)); params.addElement(new Parameter("currency", String.class, currency, null)); call.setParams(params); try { call.setMethodName("getPrice"); Response resp = call.invoke(url, ""); Parameter ret = resp.getReturnValue( ); Object value = ret.getValue( ); System.out.println("Price is " + value); } catch (SOAPException e) { System.err.println("Caught SOAPException (" + e.getFaultCode( ) + "): " + e.getMessage( )); } } }
The beginning of this application is similar to the applications we
created earlier using the Apache SOAP APIs. The code for setting up
parameters starts with a call to the setEncodingStyleURI(
)
method of the Call
object. The parameter passed to this method is a string that contains
the URI of the encoding style that should be used to
serialize the parameters.
In this case, the encoding style is
given by Constants.NS_URI_SOAP_ENC
, which is a
constant defined by the org.apache.soap.Constants
class. The value of this constant is
"http://schemas.xmlsoap.org/soap/encoding/"
, which
is the standard SOAP encoding namespace. The default value for the
encoding style of a Call
instance is
null
, so you must remember to call the
setEncodingStyleURI( )
method. Without this, the
server side will not understand the encoding scheme that should be
used to deserialize the parameters.
Next, we set up two String
variables that contain
the stock symbol[7] and the currency to use when the
getPrice( )
method is invoked.
Now we
create an instance of java.util.Vector
, which will
hold the parameters. Each parameter is held in an instance of
org.apache.soap.rpc.Parameter
. The constructor of
this class takes the parameter’s name, the class of
the object that contains the parameter value, the object that
contains the parameter value, and the encoding style URI for the
parameter. For the first parameter of the
org.apache.soap.rpc.Parameter
constructor we pass
the name stock
, which is the name used for the
first parameter of the getPrice
method of the
StockPrice
class.
The second parameter is
String.class
, which tells the constructor that the
value is passed as a Java String
instance. The
third parameter is a String
variable that contains
the actual value we’re passing:
"MINDSTRM"
. We pass null
for
the last parameter, indicating that an encoding style URI should not
be included for the XML element that will be serialized for this
parameter. Remember that we’ve already set up the
encoding style URI for the entire call when we called
call.setEncodingStyleURI( )
; unless you want to
use some other encoding style for this particular parameter, just
pass null
.
The
org.apache.soap.rpc.Parameter
instance is added to
the vector using the params.addElement( )
method.
We go through the same process for the currency
parameter, adding it to the vector, params
, as
well.
Finally, we pass the vector containing
the parameters to the Call
object using the
call.setParams( )
method. From here, the rest of
the code is familiar. The getPrice( )
service
method is invoked, and the result is printed for display.
Here’s the SOAP envelope that is sent to the server:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getPrice xmlns:ns1="urn:StockPriceService" SOAP-ENV:encodingStyle= "http://schemas.xmlsoap.org/soap/encoding/"> <stock xsi:type="xsd:string">MINDSTRM</stock> <currency xsi:type="xsd:string">USD</currency> </ns1:getPrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
As we’ve seen before, the name of the method
we’re calling is used for the child element of the
SOAP Body
, namespace-qualified by the name of the
service itself. The
SOAP-ENV:encodingStyle
attribute on the
getPrice
element applies to all of its
subelements, unless those elements include the
SOAP-ENV:encodingStyle
attribute themselves.
(That’s why we passed null
to the
org.apache.soap.rpc.Parameter
constructor.) The
two parameters are serialized as child elements of
getPrice
. Each one is explicitly typed using the
xsi:type
attribute with a value of
xsd:string
, and the parameter value is serialized
as the element data value for the associated parameter.
Now let’s take a look at what’s required to pass parameters using the GLUE APIs. We’ve already seen that GLUE binds Java interfaces to services, and the methods of those interfaces are treated like any other Java instance. So there’s really nothing profound about passing parameters in GLUE-based applications. But let’s go through the steps anyway, just to reinforce the concepts.
Assuming that the CallCounterApp
application is
still running, we can use the GLUE console to deploy the new service
on that GLUE server instance. Clicking the ADD button on the home
page of your GLUE server brings you to an empty web service editor
form. Fill in the appropriate service details, as shown in Figure 4-12.
In the figure, I’ve given the service a short
description, as well as the service’s URN
(urn:StockPriceService
). The Java class
implementing the service is StockPrice
, and the
service activation mode is Application. Click SAVE to deploy the
service.
Now we need a Java interface to
bind to this service. You can use the one generated by the GLUE
console, if you prefer; for this example, we’ll use
the interface IStockPriceService
, listed below. In
either case, remember that the names used by the GLUE console when it
generates an interface are different from the names we use when
creating the interfaces by hand.
package javasoap.book.ch4; public interface IStockPriceService { float getPrice(String stock, String currency); }
Writing an application that accesses
the StockPriceService
using the GLUE APIs is
simple. We won’t use the helper class by the GLUE
console here, although there’s certainly no reason
why you couldn’t. Here’s the
application for accessing the service:
package javasoap.book.ch4; import electric.registry.Registry; import electric.registry.RegistryException; public class StockPriceApp2 { public static void main(String[] args) throws Exception { try { IStockPriceService ctr = (IStockPriceService)Registry.bind( "http://georgetown:8004/glue/urn:StockPriceService.wsdl", IStockPriceService.class); String stock = "MINDSTRM"; String currency = "USD"; System.out.println("Price is " + ctr.getPrice(stock, currency)); } catch (RegistryException e) { System.out.println(e); } } }
Once you bind an interface to the service, you work directly with the
Java interfaces as if the service objects were local. So all we need
to do is call ctr.getPrice( )
, passing the
stock
and currency
variables as
parameters. Run StockPriceApp2
and
you’ll get the following output:
Price is 75.33
We haven’t yet looked at the SOAP messages transmitted by GLUE applications. Let’s see how they differ from those transmitted by Apache SOAP. Here’s the SOAP envelope that was transmitted by the GLUE APIs:
<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'> <soap:Body> <n:getPrice xmlns:n='http://tempuri.org/javasoap.book.ch4.StockPrice'> <arg0 xsi:type='xsd:string'>MINDSTRM</arg0> <arg1 xsi:type='xsd:string'>USD</arg1> </n:getPrice> </soap:Body> </soap:Envelope>
The
GLUE-generated SOAP envelope sets up the xsi
and
xsd
namespace identifiers the same way as Apache
SOAP. Instead of using the SOAP-ENV
identifier,
GLUE uses the name soap
, which is hardly a
significant difference; this is just a name. It can be called
anything, as long as it is assigned the value of
http://schemas.xmlsoap.org/soap/envelope
. The
encodingStyle
attribute is the same as in the
Apache SOAP examples as well. There seems to be an extra attribute,
soapenc
, that isn’t referenced
anywhere in the message, but that won’t hurt
anything.
The getPrice
element that represents the service method is not namespace-qualified
by the service name, as is the case with Apache SOAP. The namespace
is derived by appending the class name of the service implementation
class to the address http://tempuri.org/, which
is the default namespace generated by the GLUE server. Unlike the
Apache SOAP examples, the parameters are named
arg0
, arg1
, etc. Since the
method parameters are listed in the order they appear, the names are
not significant. They are explicitly typed, just as they are in the
Apache examples.
I prefer to use the actual service name to qualify the service method
name, rather than an artificial service name derived by default. This
is easy to correct; just supply a namespace property to the context
object used when the server is executed. We did the same thing to
specify the service activation mode. Here’s a
modified version of the server application (originally
CallCounterApp
), only this time we specify the
namespace to use by calling context.addProperty( )
with "namespace"
as the first parameter and
"urn:StockPriceService"
as the second parameter.
This new application publishes the
urn:StockPriceService
, so we
don’t need to go to the GLUE console.
package javasoap.book.ch4; import electric.util.Context; import electric.registry.Registry; import electric.server.http.HTTP; public class StockPriceApp3 { public static void main( String[] args ) throws Exception { HTTP.startup("http://georgetown:8004/glue"); Context context = new Context( ); context.addProperty("activation", "application"); context.addProperty("namespace", "urn:StockPriceService"); Registry.publish("urn:StockPriceService", javasoap.book.ch4.StockPrice.class, context ); } }
If you still have an instance of CallCounterApp
running, shut it down; it uses the same address and port number as
this new application. Once you start
StockPriceApp3
, the namespace used to qualify the
method name in SOAP messages will be
urn:StockPriceService
. Let’s look
at the response generated by the server for this request:
<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'> <soap:Body> <n:getPriceResponse xmlns:n='urn:StockPriceService'> <Result xsi:type='xsd:float'>75.33</Result> </n:getPriceResponse> </soap:Body> </soap:Envelope>
The response envelope is pretty
much the same as the envelope generated by Apache SOAP; the only real
difference is that the return value is contained in an element named
Result
, whereas in Apache SOAP this element is
named return
. But it’s the
structure of the response that’s important, and as
you can see the structure is the same as that of an Apache SOAP
server’s response.
Using in, out, and in/out Parameters
All of the examples so far have assumed that method
parameters contain values to be passed from the client to the
service, and that the service does not change the value of the
parameters before returning. That’s the default
behavior of SOAP RPC services; the parameters in this case are
referred to as in
parameters because the value is
passed “in” to the service. But
SOAP also supports the use of two other parameter styles. Service
methods may contain parameters that do not contain any data to be
passed from the client to the service, but do contain data (to be
read by the client) when the method returns. These are known as
out
parameters: the value of the parameter is
passed “out” of the service.
Parameters may also be in/out
, where a value is
passed in to the service and another value is passed out.
Not
all SOAP implementation support the use of out
or
in/out
parameters (Apache SOAP
doesn’t; GLUE does). That, by itself, should sound
an alarm for you. It’s not really a good idea to use
features that are not widely supported unless you are in control of
the servers as well as the clients of those servers. You may be able
to avoid the need for using out
or
in/out
parameters by defining a custom type that
contains the values you want returned, and then defining your service
methods to return an instance of that custom type. This is the
approach I take; it also avoids any confusion about the purpose of
the method. On the other hand, most recent implementations do support
in/out
parameters. In fact, many of them use the
same technique that we’re about to explore.
Now
that I’ve cautioned against using them,
let’s take a look at how out
and
in/out
parameters can be used. GLUE uses holder
classes that encapsulate data types when using out
and in/out
parameters. There are already holder
classes for the Java primitives and common classes like
java.lang.String
. For custom data types, GLUE
includes a holder
utility that generates holder
classes for you. (Keep in mind that this approach is specific to
GLUE; SOAP doesn’t dictate the API, only the XML
encoding.) Here’s a modified version of the
StockPrice
class that includes two new
methods. The first method is getPriceOut( )
; it
returns a boolean
value indicating whether the
request was successful. The first two parameters are the same as
those in the original getPrice( )
method: a stock
symbol and the currency. The third parameter is called
price
; this is an out
parameter
that has no value when the method is invoked, and contains the
resulting price when the method returns.
So getOutPrice(
)
uses an out
parameter to return the
price, which it computes using the original getPrice(
)
method. The price
parameter is an instance of
electric.util.holder.floatOut
, a holder class for
floating-point out
parameters.
The other new method is called correctSymbol(
)
, which converts a
stock symbol to an appropriate format (all uppercase characters). It
returns a boolean
indicating success or failure,
but more importantly uses a single in/out
String
parameter named symbol
.
This parameter contains a stock symbol when the method is called;
when correctSymbol( )
returns, it contains the
corrected stock symbol. To support this behavior, the symbol
parameter is declared as an instance of
electric.util.holder.java.lang.StringInOut
, a
holder class for String
in/out
parameters. The StockPriceApp3
can be used to
publish the service without any modifications.
package javasoap.book.ch4; import electric.util.holder.*; import electric.util.holder.java.lang.*; public class StockPrice { public float getPrice(String stock, String currency) { float result; // determine the price for stock and return it // in the specified currency result = (float)75.33; return result; } public boolean getOutPrice(String stock, String currency, floatOut price) { try { price.value = getPrice(stock, currency); return true; } catch (Exception e) { return false; } } public boolean correctSymbol(StringInOut symbol) { symbol.value = symbol.value.toUpperCase( ); return true; } }
Since
we’ve modified the service by adding methods,
we’ll need to modify the
javasoap.book.ch4.IStockPriceService
interface in
kind. Here’s the new interface:
package javasoap.book.ch4; import electric.util.holder.*; import electric.util.holder.java.lang.*; public interface IStockPriceService { float getPrice(String stock, String currency); public boolean getOutPrice(String stock, String currency, floatOut price); public boolean correctSymbol(StringInOut symbol); }
Now let’s create an application to use this service.
We’ll call correctSymbol( )
first
to convert a stock symbol to uppercase using an
in/out
parameter, and then call
getOutPrice( )
to retrieve the price using an
out
parameter:
package javasoap.book.ch4; import electric.registry.Registry; import electric.registry.RegistryException; import electric.util.holder.*; import electric.util.holder.java.lang.*; public class InOutApp { public static void main(String[] args) throws Exception { try { IStockPriceService ctr = (IStockPriceService)Registry.bind( "http://georgetown:8004/glue/urn:StockPriceService.wsdl", IStockPriceService.class); StringInOut stock = new StringInOut("MiNdStRm"); boolean ok = ctr.correctSymbol(stock); if (ok) { System.out.println("Symbol converted to: " + stock.value); } String currency = "USD"; floatOut price = new floatOut( ); ok = ctr.getOutPrice(stock.value, currency, price); System.out.println("Result is " + price.value); } catch (RegistryException e) { System.out.println(e); } } }
The stock symbol variable, stock
, is instantiated
with the symbol "MiNdStRm"
. We then pass that
variable, which is an instance of
electric.util.holder.java.lang .StringInOut
, to
the correctSymbol( )
method of the service
interface. When the method call returns, we expect the symbol to have
been converted to uppercase. Next we call getOutPrice(
)
, passing stock.value
as the first
parameter. The currency is passed as before, and an instance of
electric.holder.floatOut
called
price
is passed as the last parameter. When the
method returns, price
contains the stock price.
Running the example produces this output:
Symbol converted to: MINDSTRM Result is 75.33
As you can
see, both the out
and in/out
parameters were handled as expected. Before we finish up,
let’s take a look at the parts of the SOAP envelopes
passed between client and server that were affected by our use of
out
and in/out
parameters. The
call to correctSymbol( )
results in a response
envelope that looks a bit different from those we’ve
seen before. The returned boolean
value is
immediately followed by the modified parameter value.
Here’s the SOAP body for that response:
<soap:Body> <n:correctSymbolResponse xmlns:n='urn:StockPriceService'> <Result xsi:type='xsd:boolean'>true</Result> <arg0 xsi:type='xsd:string'>MINDSTRM</arg0> </n:correctSymbolResponse> </soap:Body>
The modified in/out
parameter value in XML element
arg0
comes right after the
Result
element. And, of course, you can see that
the original value has been converted to uppercase. Now
let’s look at part of the envelope sent to the
server when getOutPrice( )
was invoked. There are
only two parameter values encoded, because the third parameter is an
out
parameter and so has no value at the time of
the method invocation.
<n:getOutPrice xmlns:n='urn:StockPriceService'> <arg0 xsi:type='xsd:string'>MINDSTRM</arg0> <arg1 xsi:type='xsd:string'>USD</arg1> </n:getOutPrice>
Finally,
here’s the response part of the SOAP body that was
returned by the call to getOutPrice( )
. The value
of the price
parameter follows the result itself.
The first two parameters don’t have any values
because they are both in
parameters, and so
don’t have modified values.
<n:getOutPriceResponse xmlns:n='urn:StockPriceService'> <Result xsi:type='xsd:boolean'>true</Result> <arg2 xsi:type='xsd:float'>75.33</arg2> </n:getOutPriceResponse>
In the next chapter, we’ll begin investigating the more complex features and capabilities of SOAP and its implementations.
[1] The term
struct
is used here to mean the XML element
hierarchy used to represent a service method and its parameters. For
all you C programmers out there, don’t confuse this
with a C struct, which obviously has a different meaning.
[2] I doubt that you’ll ever encounter a method signature as part of the SOAP message. There doesn’t seem to be any industry acceptance of the concept, and it isn’t implemented by any SOAP engines I’ve encountered.
[3] As mentioned earlier, the method signature referenced in the SOAP specification does not appear to have been implemented. It is mentioned here for your information only.
[4] Actually, sending transport-specific data such as a cookie is probably not such a good idea, because it binds you to a particular transport. Some other transport-independent session data would be more appropriate.
[5] Starting interface names with an I is a common, but by no means universal, convention.
[6] GLUE provides a
utility called wsdl2java
that generates Java code
from a WSDL file, and has an option to generate the code for a
specified package. We’ll look at this utility in
Chapter 9.
[7] MINDSTRM is not a real stock symbol.
Get Java and SOAP 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.