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:
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
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.
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.
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.
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.