The Web services revolution has started. New standards and technologies enable and integrate distributed Web services. SOAP (Simple Object Access Protocol), an XML RPC (remote procedure call)/messaging protocol, underlies it all. Simple by name, simple by definition, and simple to use, SOAP lets you request a service using simple XML and receive a simple XML response.

SOAP is proving its mettle in the enterprise application space. Developers employ the protocol to integrate disparate enterprise applications, and to develop new distributed and scalable enterprise applications that use XML for cross-component messaging.

SOAP's creators, in their desire for simplicity, deliberately did not build a complex distributed object model; they left that difficult task to the application developer. However, SOAP's simplicity means a simple SOAP implementation in an enterprise application will be stateless and could fall prey to performance issues. Indeed, repeated SOAP-client calls to access server state can choke a network.

In response, we describe transparent client-side caching for SOAP services using the Business Delegate and Cache Management design patterns, and offer an implementation that completely hides from the client application the lookup, invocation, and caching of Web services. Moreover, we introduce a mechanism for automatic client-cache synchronization with the SOAP service provider.

We assume you have familiarity with SOAP installation and service deployment. We do not detail how SOAP works or how to make it work. See Resources for references to Websites where you can find more information.

Note: You can download this article's complete source code in Resources.

The problem: Web services in an application world

When developing rich Java clients with SOAP-based servers as data providers, you must address three main issues: performance, performance, and performance.

If your client application accesses the same information repeatedly from the server, you'll quickly realize the server's performance, and consequently your application's response time, is not fast enough. Further, when hundreds of client applications hit the server simultaneously, the performance degrades even faster. That's when you remember the lesson from client/server databases development: thou shalt always cache locally! Invoking a SOAP call produces costs similar to running an SQL statement in a relational database; maybe even more so. Indeed, a database likely gets accessed whenever a SOAP call is made. So a SOAP call's cost includes network latency, CPU cycles at the SOAP server, and SQL latency at the database server.

A problem, however, with caching locally: the cache might become inconsistent with the original data source; changes made to the SOAP server after the cache loads go unreflected in the cache (read inconsistency), and changes made to the cache go unreflected at SOAP server (write inconsistency).

The problem domain: stock transactions

Because the problem proves a little complex, we offer an example that highlights the need for fast response without requiring you to learn a new problem domain.

Believing that most Java developers have monitored stock ticker quotes (as spectators, if not as investors) in the last few years, for our example we picked the stock transactions domain. (The stock prices reported in the example code are from December 19, 2001.)

Our simple demonstration application deals with only a StockQuote object and three SOAP service methods. The StockQuote object has three attributes:

  • symbol: The stock symbol.
  • name: The stock's name.
  • value: The stock price's current dollar value.

At the SOAP level, the three service methods comprise:

  • Get Quote: Given a symbol, returns a StockQuote object containing the latest stock price for the symbol.
  • Add Quote: Given a StockQuote object, adds the information to the server-side database. If the database already contains an entry for the stock, the service updates the database with the new information.
  • Get All Quotes: Returns a Hashtable of the StockQuote objects stored in the database.

In our example, a Swing-based Java client displays a list of all stocks and adds or updates individual stock details. The client interacts with a SOAP server to synchronize with a server repository.

The application overview

The Demo application, as seen in Figure 1, features two SOAP services deployed at the SOAP server. First, StockQuoteService handles the stock quote methods, while the NotificationService performs notification between the business services and the clients.

Figure 1. The Demo application

The Demo application client includes two main panels. The top panel displays the stocks available at the server, while the bottom panel lets you add or update the stocks.

When you launch the client application, it registers with the SOAP server for notification updates and loads the stock-quotes table with the Get All Quotes SOAP service method.

When you add or update a stock using the edit panel, the client application updates the data repository at the SOAP server by invoking the Add Quote SOAP service method. That SOAP method call awakens the server-side notification handler, which, in turn, notifies the registered clients about the data change.

When the client receives the notification (for which it registered earlier), it expires the local cache and reloads the data by again invoking the Get All Quotes method.

The framework and assumptions

Next, let's see a high-level description of the stocks package's components, as illustrated in Figure 2. The package contains the code required to run Demo. You may skip this section and return when you're ready to compile the code.

The sub packages include:

  • soapservices: The SOAP services' definitions
  • clientservices: The common classes required by the client for SOAP service invocation and other functions
  • businessdelegates: The business delegate classes for the SOAP services
  • gui: The Swing GUI (graphical user interface) code to demonstrate the example
  • util: The StockQuote class definition
Figure 2. The package hierarchy

Note: The package names relate specifically to the Demo application. In a real-world application, the classes defined here would probably reside in different framework packages.

Let's examine what each package includes.

The soapservices package

The soapservices package includes the following classes:

  • StockQuoteService: Provides the three basic services methods that demonstrate the stock quote application
  • NotificationService: Notifies the client of stock quote changes
  • NotificationHandler: With this singleton class, the StockQuoteService class notifies the NotificationService

The clientservices package

The clientservices package includes the following classes:

  • SOAPServiceFacade: Hides the SOAP server lookup, initialization, and call invocation
  • Cache: Used by business delegates for local caching
  • ClientNotificationHandler: Notifies business delegates about cache expiry
  • CacheExpiredListener: With this interface, the ClientNotificationHandler notifies the business delegate class about cache expiry
  • DataChangeListener: The business delegates employ this interface to forward the notification to other components (like GUI components)

The businessdelegates package

The businessdelegates package includes the following classes:

  • StockQuoteServiceBusinessDelegate: The business delegate for the StockQuoteService SOAP service
  • NotificationServiceBusinessDelegate: The business delegate for the NotificationService SOAP service

The gui package

For its part, the gui package features the following classes:

  • Demo: Launches the demo client application
  • DemoFrame: The Swing frame container for all panels
  • DemoViewPanel: The view panel that displays the stock table
  • StockQuoteDataSource: The table model for the JTable component in DemoViewPanel
  • DemoEditPanel: Displays, updates, or adds selected stocks

The Demo application's services do not interact with a database; they work over a simple Hashtable that you can easily replace with a JDBC (Java Database Connectivity) interface. Moreover, to keep things simple and brief, the Demo application's code doesn't handle many exceptions. As a further caveat, this approach for handling SOAP services at the client side may prove difficult to apply in a dynamic Web services environment.

The business delegate

According to the J2EE (Java 2 Platform, Enterprise Edition) Patterns Catalog, a business delegate can "reduce coupling between presentation-tier clients and business services. The business delegate hides the underlying implementation details of the business service, such as lookup and access details of the EJB (Enterprise JavaBeans) Architecture."

The Business Delegate design pattern perfectly applies to our application in which a rich Java client interfaces with business services over SOAP. Although a business delegate for each service may seem overly complex, the pattern helps hide implementation, lookup, invocation, and caching details from the client application.

The SOAP StockQuoteService service provides stock quote services for SOAP clients. Every SOAP client, however, must handle the SOAP lookup, invocation, and caching by itself. The StockQuoteServiceBusinessDelegate, as obvious from the name, is the client-side delegate for the server-side StockQuoteService. It provides direct method-level accessors to the StockQuoteService service methods, internally performing SOAP lookup, invocation, caching, and notification.

The StockQuoteServiceBusinessDelegate class initializes a SOAPServiceFacade instance to handle all SOAP-related activities and initializes a static Cache shared by all instances of itself for local caching. The StockQuoteServiceBusinessDelegate's hook registers a client component that implements the DataChangeListener interface, enabling the client component updates when the local cache updates. The class also implements the CacheExpiredListener interface so that ClientNotificationHandler can register the business delegate for notification.

Here's the StockQuoteServiceBusinessDelegate's initializer:

public StockQuoteServiceBusinessDelegate()  throws Exception 
   soap = new SOAPServiceFacade();
      serviceName,                   // Service name
      serviceName,                   // Registry mapping for SQ
      "stocks.util.StockQuote",      // Stock quote object name
      stocks.util.StockQuote.class   // Stock quote class
   if (cache == null)
      cache = new Cache();

Next, you see a sample SOAP service-method wrapper in the StockQuoteServiceBusinessDelegate business delegate:

public boolean addQuote(StockQuote stockQuote) 
   boolean status = false;
   if(stockQuote != null) 
         System.out.println( new Date() + "> Stock Quote
            Service Business Delegate :: Add Quote -> ["
            + stockQuote + "]");
         Vector params = new Vector();
         params.addElement(new Parameter("stockQuote", 
            StockQuote.class, stockQuote, null));
         Boolean bool = (Boolean) soap.invoke("addQuote", params);
         if (bool == null) 
            status = false;
            status = bool.booleanValue();
         // Store the data in the cache
         if (status == true)
            System.out.println( new Date() + "> Stock Quote
               Service Business Delegate :: Updating Cache for
               [" + stockQuote.getSymbol() + "] ...");
            cache.addObject(stockQuote.getSymbol(), stockQuote);
   return (status);

Compare the above code to the client code that accesses the SOAP service using the BusinessDelegate, as seen here:

// Create a Stock Quote object from data in GUI components.
StockQuote stockQuote = getData(); 
if(stockQuote != null) 
   catch (Exception e) { e.printStackTrace(); } 

Figure 3's sequence diagram shows how the editing action ripples across the components.

Figure 3. The editing sequence diagram. Click on thumbnail to view full-size image.

Using a business delegate frees you, the client application developer, from worrying about the server's location, the invoked service type, how to invoke it, or whether a remote service exists at all. Instead, you focus on the client application's features.

Keep in mind that these business delegates do not require a one-to-one mapping with the Web services. They could provide business service views customized for the specific client application requirements.

The cache

The business delegate class initializes the cache in its constructor. The cache loads when the getQuote() and getAllQuote() services invoke, and updates when a StockQuote is added or updates. Moreover, the cache expires when the ClientNotificationHandler receives an expiry notification from the server.

As per Patterns in Java author Mark Grand, the Cache Management pattern's cache includes two methods: addObject() and fetchObject(). A CacheManager manages the cache using ObjectKey handles. Again, the pattern's power resides in its simplicity. In our implementation, the business delegate acts as the CacheManager since each business delegate possesses its own cache. The ObjectKey is the StockQuote object. Finally, the Cache's internal data structure is a hash table.

Page 2 of 2

Although you could easily replace the Cache with a hash table, with a real-world application's memory-usage limitations, you could better manage memory by extending the Cache (by expiring little-used cached objects, for example). You could base that expiry on simple hit counts or complicated rating systems. You could also use soft references for transient or less frequently used cached objects, which would let the JVM expire and reclaim the cached objects when the JVM runs out of memory. Or, you could create a single global cache that uses serviceName()-based ObjectKey handles to centrally manage the cache for the entire client application.

The cache methods are simple:

public synchronized void addObject(Object key, Object value) 
   if (key != null && value != null) 
      cache.put(key, value);      
public Object fetchObject(Object key) 
   if (key == null)
      return null;
   return cache.get(key);
public void expire() 
public synchronized void expire(Object key)
   if (key != null)
public boolean isEmpty() 
   return cache.isEmpty();

The sequence diagram in Figure 4 shows how data initially loads and how the cache automatically updates.

Figure 4. The data loading sequence diagram. Click on thumbnail to view full-size image.

The notification handlers

The notification subsystem ties together this example's components and invokes real-time dynamics. Two notification handlers -- the server-side ServerNotificationHandler and the client-side ClientNotificationHandler -- handle the notification subsystem.

Though SOAP over HTTP does not support bi-directional communication, we want to receive notification at the clients when changes occur at the SOAP server (e.g., when a client adds a stock quote to the server database, all registered clients should receive notification).

You could easily replace HTTP with a bi-directional protocol like BEEP (Blocks Extensible Exchange Protocol) and still send notification events back to registered clients. However, you'd lose HTTP's simplicity, flexibility, and firewall penetrating capabilities.

Instead, we opted to block SOAP service calls. The NotificationService server-side SOAP call waits until some service notifies it that some change has happened, using the ServerNotificationHandler singleton class. In this example, one service, the StockQuoteService notifies the call.

Here's ServerNotificationHandler's two critical methods:

public synchronized boolean isDataChanged() 
      catch (Exception ignored) {}
   return true;
public synchronized void dataChanged()  

At the client end, a ClientNotificationHandler singleton launches a ClientNotificationHandlerThread. The ClientNotificationHandlerThread invokes the SOAP NotificationService using the NotificationServiceBusinessDelegate and blocks. When the service returns, the ClientNotificationHandler expires the registered local caches, which in turn triggers updates to registered DataChangeListeners. The business delegates reload the caches automatically on expiry, if required.

Here's the ClientNotificationThreadHandler run() method that blocks:

public void run() 
   while (true)
      if ((nsbd != null) && (nsbd.isDataChanged())) 
         // Sleep for some time before creating the
         // NotificationServiceBusinessDelegate.

That approach, however, comes with its own overhead: it produces one idle thread for each client application waiting for notification and one idle thread for each registered client at the SOAP server side. Most enterprise applications will find that OK, as the number of clients would normally be in the hundreds -- easy for any decent server.

However, in a scenario in which thousands of clients connect to the server, the approach might prove nonviable. However, you can easily change this service to a nonblocking call. The ServerNotificationHandler could store notification events for delivery, while the ClientNotificationHandler could periodically poll the NotificationService.

We've simplified the notification subsystem at efficiency's expense. Any real-world implementation would not expire all caches in all clients all the time. In a more robust design, the SOAP services would pass information on the expired services, even what objects have expired to the ServerNotificationHandler. The ClientNotificationHandler can then expire only the affected caches at the affected business delegates.

The sequence diagram in Figure 5 shows how the notification subsystem works.

Figure 5. The notification sequence diagram. Click on thumbnail to view full-size image.

The client GUI application

The client GUI includes two functional components: First, the DemoViewPanel displays a table of the latest stock quotes and uses the StockQuoteServiceBusinessDelegate to refresh the data. Second, the DemoEditPanel lets you edit or add stock quotes and also uses the StockQuoteServiceBusinessDelegate to repost changes.

Figure 6. The Demo application GUI

With those two components, the Demo application gains a chat application feel. You can launch multiple application copies and see changes made in one simultaneously updated in the others.

SOAP opera

SOAP serves as an excellent XML messaging protocol. However, SOAP by itself can prove inefficient in a distributed application model, especially when used as a data-service provider. Because SOAP's authors kept it simple, we, as application developers, must devise a framework that addresses caching and network-traffic overhead.

In this article, you saw a simple approach, leveraging well-known design patterns, to creating transparent client-side caching of SOAP services. Any real-world application will need a more robust framework that addresses issues like database persistence, global cache management, access-based cache-hits management, and practical exception handling. But that's best left to you.

Ozakil Azim heads up development for netForensics, a 100 percent Java, distributed, and scalable enterprise product for security information management. Azim is also working on dhvani, an open source Java text-to-speech system for Indian languages. Araf Karsh Hamid manages the development of the netForensics application framework and core components. Karsh has been developing software applications for more than 10 years.

Learn more about this topic