Revolutionary RMI: Dynamic class loading and behavior objects

Find out how RMI can help you define extensible, distributed object-oriented frameworks

1 2 3 Page 2
Page 2 of 3
  1. The user of Client 1 draws an X using the mouse. The application creates a new ScribbleX object to encapsulate the required data and drawing behavior. This object is added to the drawing by calling the addShape(ScribbleShape s) method in the server.

  2. The server receives the parameter but can't locate the ScribbleX class locally, so it uses codebase information marshalled with the parameter to load the class from the Client 1 host HTTP server. (RMI performs this dynamic class loading automatically.)

  3. The server notifies each client about the new ScribbleShape by invoking the addShape(ScribbleShape s) callback. One of the clients happens to be Client 2.

  4. Client 2 cannot locally locate the ScribbleX class. RMI is smart enough to remember the codebase URL from which the class was originally loaded, so it marshalls the codebase URL with the parameter. So, the RMI system on Client 2 remotely loads the class from the HTTP server on Client 1. Magic!

Programming with behavior objects

Enough high-level discussion, let's look at some code. Here's a class diagram of the entire application.

Figure 5: Click here for a closer look at the UML class diagram of the Scribble application classes

The basis for constructing the application is two remote interfaces: ScribblePad and ScribblePadServer. A ScribblePad represents a client that is typically presenting the drawing and possibly allowing the user to place new shapes on the drawing. The ScribblePad interface is defined as:

public interface ScribblePad extends Remote {
   public void addShape(ScribbleShape s) throws RemoteException;
   public void clear() throws RemoteException;
}

Notice how the ScribblePad.addShape() method takes a ScribbleShape instance, which is a nonremote interface. This indicates the parameter is a behavior object and will be passed from the client to the server through serialization. What types of shapes can be added to the drawing? A square, circle, picture, animation, or any other shape as long it supports the ScribbleShape interface.

The ScribblePad interface is implemented by the ScribbleViewer class. The ScribbleViewer instance is displayed by a ScribbleViewerFrame object, which serves as the basis for the Scribble Viewer applications. The ScribblePadImpl class extends the ScribbleViewer class and adds editing capabilities. The Scribble Pad application is formed by placing a ScribblePadImpl object inside of a ScribblePadFrame instance.

The ScribblePadServer represents the server that maintains a list of ScribblePad clients and a list of ScribbleShape instances added to the drawing:

public interface ScribblePadServer extends ScribblePad {
   public Vector getShapes() throws RemoteException;
   public void addScribblePad(ScribblePad p) 
      throws RemoteException;
   public void removeScribblePad(ScribblePad p) 
      throws RemoteException;
}

Take a look at the following figure, which illustrates the calls involved with adding a shape to the drawing.

Figure 6: Adding a shape to the drawing

Things should be starting to come into focus; look in the mirror, if you're working along with me your face still has a strange look -- albeit not as bad as "the look."

Before getting into methods of running the different pieces of this application, let me walk you through a few of the coding high points. I am not going to list and discuss all the code here; you can download and view the source files from Resources. Let's look at the code that executes Steps 4, 5, and 6 from Figure 6.

When the user presses a mouse button, it executes a mouse event handler in the ScribblePadImpl class (Step 4 in the above diagram):

public class ScribblePadImpl extends ScribbleViewer {
   int fShapeType = LINE;
   Point fP1;   
   //inherited from ScribbleViewer
   //ScribblePadServer fServer;
   public static final int LINE = 1;
   public static final int POINT = 2;
   public static final int X = 3;
   public ScribblePadImpl(ScribblePadServer s) 
   throws RemoteException         
   {
       super(s);
       addMouseHandlers();  //not shown in listing
   }
   public void setShapeType(int t) {
      fShapeType = t;
   }
   //other support methods here      
   void addShapeToServer(ScribbleShape s) {
      try {
         fServer.addShape(s);
      } catch(RemoteException ex) {
        ex.printStackTrace();
      }
   }
   //user pressed the mouse indicating the point to create an
   //instance of the currently selected shape
   public void mousePressed(MouseEvent e) {
      Point p;
      switch (fShapeType) {
         case X:
            p = new Point(e.getX(), e.getY());
            ScribbleX x = new ScribbleX(p, Color.blue);
            addShapeToServer(x);
            break;
         case LINE:
            //start of a new line
            fP1 = new Point(e.getX(), e.getY());
            break;
         case POINT:
            p = new Point(e.getX(), e.getY());
            ScribblePoint pt = new ScribblePoint(p, Color.blue, 
                                                 true);
            addShapeToServer(pt);
            break;
      }
   }
   //other mouse handler code here . . .
}

The mousePressed() method is called whenever the user presses a mouse button. The method determines the type of shape to create, creates the shape, and then adds it to the server. Assuming the fShapeType instance variable was previously set to ScribblePadImpl.X by the containing ScribblePadFrame instance menu handlers (see the class diagram), the mousePresssed() method would generate and add a ScribbleX instance to the drawing. Of course, ScribbleX implements the ScribbleShape interface as shown previously.

The fServer.addShape() in addShapeToServer() will transfer control across the network to the ScribblePadServerImpl.addShape() method of the Scribble Server application:

public class ScribblePadServerImpl extends UnicastRemoteObject 
implements ScribblePadServer 
{
   private Vector fShapes;
   private Vector fClients;
   protected JTextArea fLog;
  public void addShape(ScribbleShape s) {
      Class cl = s.getClass();
      String annotation = RMIClassLoader.getClassAnnotation(cl);
      fLog.append("Adding shape: " + s.getClass().getName() +  
                  " loaded from " + annotation + "\n");
      fShapes.addElement(s);
      sendToClients(s);
   }

   protected void sendToClients(ScribbleShape s) {
      Vector v = (Vector)fClients.clone();
      ScribblePad p; 
      //iterate clients and pass shape to each one
      //assume com failure means client no longer valid
      for (int i=v.size()-1; i>=0; i--) {
         p = (ScribblePad)v.elementAt(i); 
         try {
            p.addShape(s);
         } catch(Exception ex) {
            fClients.removeElement(p);
            fLog.append("Error adding shape to client: " + 
                        ex + "\n");
         }
      }
   }    
   //other methods here       
}

Note how the server is written without requiring the actual types that'll be operated on in addShape(). The method declares the parameter using the nonremote ScribbleShape interface, so the parameter may be a ScribbleLine, ScribblePoint, ScribbleX, or ScribbleVirus. More on this last parameter later, but just to reiterate, the parameter is a behavior object because the server will be passed a copy of the object and all methods will run within the server's address space.

The addShape() method performs three basic operations:

  • Gets the annotated URL from which the class was remotely loaded. Because the server doesn't have local access to any shape classes, an annotation will always contain a URL. Refer to the server screenshot above to see the annotated URL for the received shapes.

  • Adds the shape to the Vector of shapes that make up the drawing.

  • Broadcasts the new shape to all registered ScribblePad instances. As a side note, the RMI stubs of the clients will be loaded using the same means as the shapes.

The ScribblePad.addShape() method invocation in sendToClients() transfers control to the remote ScribblePad implementations. For our client applications, the addShape() method is implemented in the ScribbleViewer class:

public class ScribbleViewer extends JPanel implements ScribblePad {
    ScribblePadServer fServer;
    Image fImage;
    Graphics fG;
    Dimension fSize = new Dimension(-1, -1);
   Vector fShapes = new Vector();
    public void addShape(ScribbleShape s) {
      fShapes.addElement(s);
       s.draw(fG);
       repaint();
    }    
    //other methods here - including paint()
 }

The ScribbleViewer.addShape() method is written using only the ScribbleShape interface. The ScribbleViewer class maintains an offscreen image of the current drawing, fImage, and a graphics object that draws on the offscreen image, fG. The method performs three basic operations:

  • Adds the shape to the local copy of the drawing shapes.

  • Draws the new shape onto the drawing using the ScribbleShape.draw() method, passing in the offscreen graphics context. In this case, a ScribbleX is passed into the method from the server and the Java VM will invoke the ScribbleX.draw() method.

  • Requests the viewer repaint the screen using the updated offscreen image.

Like ScribblePadServer, the viewer will remotely load the ScribbleX class from the annotated codebase URL during the argument unmarshalling process.

Experiencing the power of RMI

Now for the ultimate test: using the application. If you wish to follow along, you can download and extract application files from Resources. I will run three processes on three machines, but you can run the application on a single machine (the distribution separates the files into three directories, dynamic\server\, dynamic\pad\, and dynamic\viewer\), as shown in Figure 4. Running the application on a single machine decreases the setup complexity, but it sure creates a lot of processes.

I have three machines:

  • STEELRAIN, which runs the Scribble Server application
  • BUBBA, which runs the Scribble Pad editor application
  • FRED, which runs the Scribble Viewer application

As with any distributed application, the server must be started before the client processes. The following steps set up the server host STEELRAIN:

Step 1. Start the HTTP process that will serve the class files to remote RMI objects. I will use a simple Java class that understands HTTP well enough to deliver Java class files to requesting processes, called httpd. In a production-level environment, you'll want to use a commercial-quality Web server. Simply start in the Java interpreter in the directory root of your class files and specify the port on which it should run.

Note: All of my machines are Win32 machines. The start command you see in the following code runs processes in a new, independent command shell. start is the Windows equivalent of ending a command line with & in Unix.

> start java httpd 8000

Step 2.

Start the

rmiregistry

process to enable clients to locate the

ScribblePadServer

object.

> cd ..
> start rmiregistry
> cd server

The registry must be run such that it cannot locate any stub files using the CLASSPATH environment variable. If it can load classes from the filesystem, the marshalled stub will not be annotated with the codebase URL.

Step 3. Start the server process.

> java -Djava.rmi.server.codebase=http://steelrain:8000/    >>>
       -Djava.security.policy=dynamic.policy ScribblePadServerImpl

Note: It's imperative the codebase URL end with a slash (/).

The -D property specifies a system property on the command line. In this case, we're specifying the RMI codebase and the name of the security policy file.

Step 4. Interestingly, the registry requested some Scribble Server class files from our HTTP server. The registry process is just another RMI object that exists on a well known port. Processes wishing to expose RMI objects pass a remote reference, a stub, to the registry as a method argument that requires it to unmarshall the parameter value. The registry locates and loads the required class files from the filesystem using CLASSPATH, or the invoking process's codebase URL if it cannot be loaded locally. Consequently, the HTTP server console output on STEELRAIN reads:

> java httpd 8000
httpd running on port: 8000
document root is: C:\dynamic\server\.
Request from STEELRAIN: ScribblePadServerImpl_Stub.class
Request from STEELRAIN: ScribblePadServer.class
Request from STEELRAIN: ScribblePad.class
Request from STEELRAIN: ScribbleShape.class

Time to roll my chair over to BUBBA and run the Scribble Pad editor. The client machines don't require a registry process because the Scribble Server serves as the sole coordination point. Step 1. Start the HTTP process that will serve the class files to remote RMI objects in the dynamic/client directory. This server will listen on port 8001. I'm running this on a separate port so you can follow these instructions and run this example on a single machine.

> start java httpd 8001

Step 2. Start the Scribble Pad process.

> java -Djava.rmi.server.codebase=http://bubba:8001/    >>>
  -Djava.security.policy=dynamic.policy ScribblePadFrame STEELRAIN

Note: The optional command-line parameter is the Scribble Server host.

Remember, the codebase URL must end with a slash (/).

This command will create a Scribble Pad frame window if it is successful. The Scribble Server will dynamically load the Scribble Pad's stub class file from BUBBA's HTTP server:

> java httpd 8001
httpd running on port: 8001
document root is: E:\dynamic\client\.
Request from STEELRAIN: ScribbleViewer_Stub.class

Notice the request for the stub came from STEELRAIN, and we're running on BUBBA! The stub file is based on the ScribbleViewer class because it implements the remote interface ScribblePad and ScribblePadImpl extends ScribbleViewer (see the class diagram). Now draw a couple of shapes and inspect the output of httpd.

> httpd running on port: 8001
document root is: E:\dynamic\client\.
Request from STEELRAIN: ScribbleViewer_Stub.class
Request from STEELRAIN: ScribbleX.class
Request from STEELRAIN: ScribblePoint.class
Request from STEELRAIN: ScribbleLine.class

The Scribble Server dynamically loaded the class files from BUBBA because it couldn't find them on STEELRAIN using its CLASSPATH.

I hate to be rude and leave the room, but it's time to run the Scribble Viewer on FRED. Remember, this viewer doesn't have local access to the shapes, and it'll connect to the Scribble Server RMI object not directly to the Scribble Pad RMI object.

1 2 3 Page 2
Page 2 of 3