Recipe 22.5 Program: RMI Callbacks

One major benefit of RMI is that almost any kind of object can be passed as a parameter or return value of a remote method. The recipient of the object will not know ahead of time the class of the actual object it will receive. If the object is of a class that implements Remote (java.rmi.Remote), the returned object will in fact be a proxy object that implements at least the declared interface. If the object is not remote, it must be serializable, and a copy of it is transmitted across the Net. The prime example of this is a String. It makes no sense to write an RMI proxy object for String. Why? Remember from Chapter 3 that String objects are immutable! Once you have a String, you can copy it locally but never change it. So Strings, like most other core classes, can be copied across the RMI connection just as easily as they are copied locally. But Remote objects cause an RMI proxy to be delivered. So what stops the caller from passing an RMI object that is also itself a proxy? Nothing at all, and this is the basis of the powerful RMI callback mechanism.

An RMI callback occurs when the client of one service passes an object that is the proxy for another service. The recipient can then call methods in the object it received and be calling back (hence the name) to where it came from. Think about a stock ticker service. You write a server that runs on your desktop and notifies you when your stock moves up or down. This server is also a remote object. You then pass this server object to the stock ticker service, which remembers it and calls its methods when the stock price changes. See Figure 22-2 for the big picture.

Figure 22-2. RMI callback service

The code for the callback service comes in several parts. Because there are two servers, there are also two interfaces. The first is the interface for the TickerServer service. There is only one method, connect( ), which takes one argument, a Client:

package com.darwinsys.callback; import com.darwinsys.client.*; import java.rmi.*; public interface TickerServer extends Remote {     public static final String LOOKUP_NAME = "TickerService";     public void connect(Client d) throws RemoteException; }

Client is the interface that displays a stock price change message on your desktop. It also has only one method, alert( ), which takes a String argument:

package com.darwinsys.client; import java.rmi.*; /** Client -- the interface for the client callback */ public interface Client extends Remote {     public void alert(String mesg) throws RemoteException; }

Now that you've seen both interfaces, let's look at the TickerServer implementation (Example 22-5). Its constructor starts a background thread to "track" stock prices; in fact, this implementation just calls a random number generator. A real implementation might use a third RMI service to track actual stock data. The connect( ) method is trivial; it just adds the given client (which is really an RMI proxy for the client server running on your desktop). The run method runs forever; on each iteration, after sleeping for a while, it picks a random stock movement and reports it to any and all registered clients. If there's an error on a given client, the client is removed from the list.

Example 22-5.
package com.darwinsys.callback; import com.darwinsys.client.*; import java.rmi.*; import java.rmi.server.*; import java.util.*; /** This is the main class of the server */ public class TickerServerImpl     extends UnicastRemoteObject     implements TickerServer, Runnable {     ArrayList list = new ArrayList( );     /** Construct the object that implements the remote server.      * Called from main, after it has the SecurityManager in place.      */     public TickerServerImpl( ) throws RemoteException {         super( );    // sets up networking     }     /** Start background thread to track stocks :-) and alert users. */     public void start( ) {         new Thread(this).start( );     }     /** The remote method that "does all the work". This won't get      * called until the client starts up.      */     public void connect(Client da) throws RemoteException {         System.out.println("Adding client " + da);         list.add(da);     }     boolean done = false;     Random rand = new Random( );     public void run( ) {         while (!done) {             try {                 Thread.sleep(10 * 1000);                 System.out.println("Tick");             } catch (InterruptedException unexpected) {                 System.out.println("WAHHH!");                 done = true;             }             Iterator it = list.iterator( );             while (it.hasNext( )){                 String mesg = ("Your stock price went " +                     (rand.nextFloat( ) > 0.5 ? "up" : "down") + "!");                 // Send the alert to the given user.                 // If this fails, remove them from the list                 try {                     ((Client) )).alert(mesg);                 } catch (RemoteException re) {                     System.out.println(                         "Exception alerting client, removing it.");                     System.out.println(re);                     it.remove( );                 }             }         }     } }

As written, this code is not threadsafe; things might go bad if one client connects while we are running through the list of clients. I'll show how to fix this in Recipe 24.5.

This program's "server main" is trivial, so I don't include it here; it just creates an instance of the class we just saw and registers it. More interesting is the client application shown in Example 22-6, which is both the RMI client to the connect( ) method and the RMI server to the alert( ) method in the server in Example 22-5.

Example 22-6. Callback
package com.darwinsys.client; import com.darwinsys.callback.*; import*; import java.rmi.*; import java.rmi.server.*; /** This class tries to be all things to all people:  *    - main program for client to run.  *    - "server" program for remote to use Client of  */ public class ClientProgram extends UnicastRemoteObject implements Client {     protected final static String host = "localhost";     /** No-argument constructor required as we are a Remote Object */     public ClientProgram( ) throws RemoteException {     }     /** This is the main program, just to get things started. */     public static void main(String[] argv) throws IOException, NotBoundException {         new ClientProgram( ).do_the_work( );     }     /** This is the server program part */     private void do_the_work( ) throws IOException, NotBoundException {         System.out.println("Client starting");         // First, register us with the RMI registry         // Naming.rebind("Client", this);              // Now, find the server, and register with it         System.out.println("Finding server");         TickerServer server =              (TickerServer)Naming.lookup("rmi://" + host + "/" +             TickerServer.LOOKUP_NAME);         // This should cause the server to call us back.         System.out.println("Connecting to server");         server.connect(this);         System.out.println("Client program ready.");     }     /** This is the client callback */     public void alert(String message) throws RemoteException {         System.out.println(message);     } }

In this version, the client server alert( ) method simply prints the message in its console window. A more realistic version would receive an object containing the stock symbol, a timestamp, and the current price and relative price change; it could then consult a GUI control to decide whether the given price movement is considered noticeable and pop up a JOptionPane (see Recipe 14.7) if so.

Java Cookbook
Java Cookbook, Second Edition
ISBN: 0596007019
EAN: 2147483647
Year: 2003
Pages: 409
Authors: Ian F Darwin

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: