There are a number of application areas in which it's useful to
access multiple Web services that implement the same interface. For
example, consider buying a book from an online bookstore. Suppose
several bookstores implement a Web service that provides information
about the price and availability of books. A user can employ a
software agent that contacts the Web services of these bookstores to
find the one with the lowest price for a particular book. Another
example is a user who wants to buy a digital camera with a particular
set of features such as 2 megapixel resolution and a battery life of
two hours. The user's software agent can contact the Web services of
several online vendors to find those that sell cameras with the
desired features. Yet another example is a user who is checking
online job listings. The user's agent can contact the Web services of
several online job posting sites and combine the results to form a
single comprehensive list of job postings.
The situations in which it can be beneficial for a software agent to
access multiple Web service instances can be categorized as follows:
- Choosing a vendor with the best value for some feature:
Examples include: the bookstore with the lowest price for a book, a
car dealer with a car that gets the most miles per gallon, the vendor
that sells sleeping bags with the lowest temperature rating.
- Finding vendors that carry items with particular features:
Examples include: vendors selling digital cameras with 2 megapixel
resolution and a battery life of two hours, an airline with flights
from New York to San Diego on March 21.
- Combining information from several sources: Examples include:
searching several online job posting sites to form an aggregate
listing of jobs, getting book reviews from multiple sites, gathering
news about a company from several sources.
- Achieving fault tolerance: Examples include: getting stock
quotes from an alternate service if the primary service is down,
verifying credit card data from a backup service.
- Dividing up work: Examples include: a chess program that
employs multiple service instances to examine different portions of
its search tree, a genetic algorithm that distributes computation
among multiple service instances.
- Improving response time by concurrently calling several
service instances and using whichever returns a result first: An
example is getting real-time stock quotes by sending requests to
several services.
One approach to handling these situations is to access Web services
sequentially. For example, when looking for a bookstore with the best
price an agent could first contact a service at Fatbrain.com. After
it has received a price, it would then request a price from
Amazon.com, and so on.
This sequential approach results in unnecessarily long delays for the
end user. A better approach is to send requests concurrently to all
the bookstores. This article develops a reusable Java component
designed for this task.
The approach taken here makes use of the IBM Web Services Toolkit
(IBM WSTK), although the same concepts apply to other Web service
toolkits such as GLUE and Idoox. The IBM WSTK provides a utility to
convert a WSDL interface for a Web service into a Java proxy class.
The proxy class acts as a client to the Web service and converts Java
method calls to SOAP calls. The class has methods corresponding to
the Web service methods described in the WSDL. A user of the class
simply calls the appropriate method and waits for it to return a
value.
The proxy class generated by the IBM WSTK is intended for use with a
single Web service instance. When a method on the class is called, it
blocks the client's thread until the Web service returns a result.
This article presents a class called MultiSer viceProxy that makes
use of the IBM WSTK proxy classes internally. The MultiService Proxy
class provides its clients with a straightforward way to concurrently
access multiple Web service instances. MultiServiceProxy doesn't
block its client's thread while waiting for services to return
results. Instead, it allows the client to register event listeners
that will be invoked when the Web services return results.
Using the MultiServiceProxy Class
The MultiServiceProxy class allows clients to concurrently call
multiple Web service instances using the same methods they use
to call a single Web service instance. Listing 1 shows an example.
The key statement in this example is the call to
MultiServiceProxy.newInstance().newIn stance() returns an object that
allows clients to call multiple Web service instances concurrently.
The statement multiBookstore.get Price("OOSC2") causes two threads to
be spawned. One thread calls getPrice() on the Web service at http://host1.com:8080/soap/servlet/rpcrouter and the other thread
calls getPrice() on the Web service at http://host2.com:8080/soap/servlet/rpcrouter.multiBookstore.getPrice("OOSC2")
returns to the client immediately without waiting for the threads to
finish their tasks. At some later time the threads will receive
results from the Web service instances. At that time the threads will
pass the results back to the client by calling event listeners that
the client registered with MultiServiceProxy.
The Bookstore_ServiceProxy class passed to newInstance() is a proxy
class generated by the IBM WSTK for accessing the bookstore Web
service. It contains a method, getPrice(), that requests a price from
a single Web service instance and doesn't return until a reply is
received from the service. TheMultiService Proxy class creates one
instance of Bookstore_ ServiceProxy for each Web service it contacts.
These instances are created in separate threads so that the multiple
Web service instances can be contacted concurrently.
As mentioned above, the MultiServiceProxy class passes results back
to the clients via event listeners. Clients can implement the
IIncrementalServiceListener interface to be notified when Web service
results arrive. The code fragments in Listing 2 illustrate this.
In this code fragment the statement multiBookstore.getPrice("OOSC2")
will cause asynchronous calls to be made to multiple Web service
instances. In the typical case multiBookstore.getPrice("OOSC2") will
return before the Web services have returned values and the message
"Back from multiBookstore. getPrice()" will be displayed. At some
later time one of the Web services will return a value. That will
cause SampleIncrementalListener.result() to be called. Some time
after this, another Web service will return a value and SampleIncre
mentalListener.result() will be called again.
The ServiceResultEvent class contains the result from the Web service
and the URL identifying the Web service that the result came from. It
also contains error information that can be queried to see if there
was an error while contacting the service.
The above example uses the IIncremen talServiceListener interface to
receive separate result notifications for each Web service. There is
also an IBatchServiceListener interface to receive notifications of
Web service results. IBatchServiceList ener contains a results()
method that is invoked just once after all Web service instances have
returned results. The results are passed as a Collection of Serv
iceResultEvent instances to the results() method. Table 1 summarizes
the primary classes and interfaces.
Internals of the
MultiServiceProxy Class
The MultiServiceProxy class uses Java's dynamic proxies to present
clients with the same interface as the proxy classes generated by the
IBM WSTK. (Note that the proxy classes generated by the IBM WSTK are
proxies in the traditional sense of the word and have nothing to do
with Java's dynamic proxies.) The classes generated by the IBM WSTK
work with a single Web service instance at a time while the dynamic
proxies created by the MultiServiceProxy class work with multiple Web
service instances concurrently.
Listing 3 shows the newInstance() method of MultiServiceProxy. It
calls Java's Proxy.new ProxyInstance() method to create a dynamic
proxy class implementing the same interface as the class from the IBM
WSTK (which is passed in as the WebServiceProxyClass argument). The
last argument to newProxy Instance() is a MultiServiceProxy instance
whose invoke() method will be called whenever one of the methods of
the dynamic proxy class is called.
An earlier example contained the statement
multiBookstore.getPrice("OOSC2") where multiBookstore is a dynamic
proxy returned by newProxyInstance(). We never explicitly wrote a
client-side class with a getPrice() method. The Bookstore_Service
Proxy class generated by the IBM WSTK has
a getPrice() method but multiBookstore is
not an instance of Bookstore_ServiceProxy
so multiBookstore.getPrice() must be calling some other method.
multiBookstore is an instance of a dynamic proxy class created behind
the scenes by Java when Proxy.newProxyInstance() was called. After
creating the class, Proxy.newProxy Instance() then created an
instance of the class and returned it. The dynamic proxy class
implements the same interface as Books tore_Service Proxy because
Bookstore_Ser viceProxy was passed as an argument to Proxy.new
ProxyInstance().
The dynamic proxy class's getPrice() method doesn't do any real work
itself; it just delegates to MultiServiceProxy.invoke(). MultiService
Proxy.invoke() can tell that the original method call was to
getPrice() by examining its Method argument.
The Invocation Handler
The invocation handler MultiService Proxy.invoke() creates an
instance of the Method Call Context class. MethodCall Context stores
the parameters and results for a particular Web service method
invocation. MethodCall Context.call Method() spawns a thread (via a
Service AccessThread class) for each Web service to be called. Each
thread creates an instance of Bookstore_ServiceProxy and makes a
blocking call to Bookstore_ ServiceProxy. get Price(). When Book
store_Service Proxy.getPrice() returns, the thread adds the return
value to an internal collection in the MethodCall Context instance.
When a value is added to the collection a check is made to see if we
have received results from all Web service instances. If so, the
results will be sent to all registered IBatchService Listeners. In
addition the current result will be sent to all registered
IIncremental Service Listeners regardless of whether we have received
results from all services yet.
Figure 1 is a sequence diagram showing an example of calling
getPrice() with two Web service instances.
Figure 1:
Sample Client and Web Service
This section describes a sample client that contacts multiple
instances of a Web service. We will use the bookstore Web service
mentioned earlier. A GUI client will use the Multi Ser viceProxy
class to call the get Price() method of several instances of the
service. Figure 2 shows the GUI client. It allows the URLs of the Web
services to be entered. There are radio buttons for selecting either
an IIncremental Service Lis tener or an IBatch ServiceListener.
Figure 2:
Clicking the "Call Web services" button uses the MultiServiceProxy
class to call getPrice() on the Web services whose URLs were entered
in the listbox. If the IIncrementalServiceListener radio button was
selected then prices returned by services will be added to the table
grid as they arrive. The sample bookstore Web service has a random
delay in its getPrice() method to simulate the delay that would be
experienced calling a real service across the Internet. This random
delay causes values to be added to the results table one at a time
with a noticeable delay between values.
If the IBatchServiceListener radio button is selected and the "Call
Web services" button is clicked the result table will remain empty
for some random time period and then all values will appear at once.
Creating the Proxy
In this section we will briefly describe the high-level steps used to
create the Web service proxy class Bookstore_ServiceProxy. We also
mention a minor change that needs to be made to the Book store_
ServiceProxy.java file after it is generated.
The first step is to write the class Bookstore, which implements the
bookstore Web service. Then the wsdlgen utility from the IBM WSTK is
used to create wsdl files describing the service. At this point we
have the files needed for the server side of the Web service.
The next step is to run the proxygen utility from the IBM WSTK on the
wsdl files. Proxygen creates the file Bookstore_Service Proxy.java.
The MultiServiceProxy class (and the dynamic proxies it employs)
requires that Bookstore_ServiceProxy implement an interface
containing the getPrice() method. Since the IBM WSTK doesn't generate
such an interface, we manually create an IBookstore interface with
the getPrice() method. The Bookstore_ServiceProxy. java file must
then be edited to add "implements IBookstore" to the declaration of
the class.
Conclusion
This article has developed a Java component that allows application
programmers to easily call methods on multiple Web service instances
concurrently. This component can be used in a variety of applications
such as finding the best price from several vendors or merging
information from several sources.
The MultiServiceProxy component builds on the proxy classes generated
by the IBM WSTK. This reduces the complexity of MultiServiceProxy but
introduces a dependency on the IBM WSTK. However, the same general
approach can be used with proxy classes generated by other Web
service toolkits (such as GLUE and Idoox) with minor modifications.
MultiServiceProxy allows clients to use the same interface for
accessing multiple Web service instances that they use for accessing
a single instance. This is done with Java's dynamic proxies so that
application programmers don't have to write custom code for each Web
service. Multi ServiceProxy also hides most of the threading issues
involved in calling multiple Web service instances from the
application programmer.
References
Author Bio
Joe Verzulli is an independent consultant in the New York area
specializing in Java and XML. He has worked with Java since JDK 1.0.2. jverzulli@hotmail.com
Concurrently Accessing Multiple Web Service Instances, by Joe Verzulli
WSJ Vol 02 Issue 03 - pg.63
Listing 1
IBookstore multiBookstore;
String urls =
{"http://host1.com:8080/soap/servlet/rpcrouter",
"http://host2.com:8080/soap/servlet/rpcrouter"}
multiBookstore =
(IBookstore) MultiServiceProxy.newInstance(
Bookstore_ServiceProxy.class,
urls,
listener);
multiBookstore.getPrice("OOSC2");
Listing 2
class SampleIncrementalListener
implements IIncrementalServiceListener
{
public void result(ServiceResultEvent r)
{
System.out.println("Got result
"
+ r.getValue()
+ " from Web service "
+ "at URL " + r.getURL());
}
}
.
.
.
// The following code is in the client that will
// contact multiple web services.
IIncrementalServiceListener listener =
new SampleIncrementalListener();
multiBookstore =
(IBookstore) MultiServiceProxy.newInstance(
Bookstore_ServiceProxy.class,
urls,
listener);
multiBookstore.getPrice("OOSC2");
System.out.println("Back from "
+ "multiBookstore.getPrice()");
Listing 3
public static synchronized Object newInstance(
Class webServiceProxyClass,
String urls[],
IServiceListener listeners[])
{
return Proxy.newProxyInstance(
webServiceProxyClass.getClassLoader(),
webServiceProxyClass.getInterfaces(),
new MultiServiceProxy(
webServiceProxyClass,
urls,
listeners));
}
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com