Exploring Java RMI-based distributed applications in JBuilder

This is a feature of JBuilder Professional and Enterprise.

Note that RMI functionality is available to you in every version of JBuilder, because the tools you need to create an RMI application ship with Java. The steps in this tutorial will need to be modified only slightly when using the RMI command-line tools instead of the JBuilder development environment.

Remote Method Invocation (RMI) enables you to create distributed Java-to-Java applications, in which the methods of remote Java objects can be invoked from other Java virtual machines, possibly on different hosts. A Java program can make a call on a remote object once it obtains a reference to the remote object, either by looking up the remote object in the bootstrap naming service provided by RMI or by receiving the reference as an argument or a return value. A client can call a remote object in a server, and that server can also be a client of other remote objects. RMI uses object serialization to marshal and unmarshall parameters and does not truncate types, supporting true object-oriented polymorphism.

Caffeine is another way to to define interfaces in Java. The java2iiop compiler allows you to stay in an all-Java environment, if that's what you want. The java2iiop compiler takes your Java interfaces and generates IIOP-compliant stubs and skeletons. Part of the advantage of using the java2iiop compiler is that, through the use of extensible structs, you can pass Java serialized objects by value. This is a feature of JBuilder Enterprise.

There may be some sample RMI applications installed in the /jbuilder/samples/rmi directory of your JBuilder installation. See their accompanying HTML files for a description of the sample application.

Note: If you downloaded JBuilder, you also need to download the Samples Pack in order to have the samples.

An example of writing a distributed database application using RMI and DataSetData may be found in the /jbuilder/samples/DataExpress/StreamableDataSets directory of your JBuilder installation. This example includes a server application that will take data from the employee sample table and send the data via RMI in the form of DataSetData. A client application will communicate with the server through a custom Provider and a custom Resolver, and display the data in a grid.

Tutorial: creating a Java RMI-based distributed application in JBuilder

This chapter first shows you the steps to follow to create a distributed version of the classic "Hello World" program using Java Remote Method Invocation (RMI). Other examples using RMI may be located on the Java Remote Method Invocation (RMI) Home Page, at http://java.sun.com/products/jdk/rmi/index.html.

The distributed "Hello World" example uses an applet to make a remote method call to the server from which the applet was downloaded to retrieve the message "Hello World!". When the applet runs, "Hello World!" is displayed on the client browser.

This tutorial contains these three lessons:

The source code used in this tutorial can be downloaded from the http://java.sun.com Web site, or copied from this tutorial into the appropriate files in JBuilder. The files are:

Note: For the remainder of this tutorial, the terms "remote object implementation", "object implementation," and "implementation" may be used interchangeably to refer to the class, examples.hello.HelloImpl, which implements a remote interface.


Write the Java source and HTML files

Because the Java language requires a mapping between the fully-qualified package name of a class file and the directory path to that class, before you begin writing Java code you need to decide on package and directory names. This mapping allows the Java compiler to know the directory in which to find the class files mentioned in a Java program. For the programs in this tutorial, the package name is examples.hello and the source directory is $HOME/jbproject/examples/hello. In this case $HOME points to the location of the user's home directory, so for the default installation on Solaris, the directory would be something like usr/home/<username>/jbproject/examples/hello. On Windows NT, the directory would be something like c:/WINNT/profiles/<username>/jbproject/examples/hello.

To do this in JBuilder, create a new project to hold the application, and then set its properties. To create a new project,

  1. Select File|New Project from the menu. The Project Wizard displays.
  2. Change the Project Name and Project Directory Name to Hello. Click Finish.

This step creates the development directory and package and provides a container for adding the other pieces of the application.

The following topics in this section create the applicable interfaces and implementations.

Define the functions of the remote class as an interface written in the Java programming language

In the Java programming language, a remote object is an instance of a class that implements a remote interface. Your remote interface will declare each of the methods that you would like to call remotely. Remote interfaces have the following characteristics: To create the interface definition for the remote interface,
  1. Right-click the project file Hello.jpx.
  2. Select Add Files/Packages.
  3. Enter Hello.java in the File Name field.
  4. Click OK. A dialog asks if you want to create the file, click OK.
  5. Double-click the file Hello.java. It displays, with the Source tab selected.
  6. Enter the following code in the Source window:

    
        package examples.hello;
    
        import java.rmi.Remote;
        import java.rmi.RemoteException;
    
        public interface Hello extends Remote {
         String sayHello() throws RemoteException;
        }
    

    The interface contains just one method, sayHello, which returns a string to the caller.

  7. Select File|Save All.

Because remote method invocations can fail in very different ways from local method invocations (due to network related communication problems and server problems), remote methods will report communication failures by throwing a java.rmi.RemoteException. If you want more information on failure and recovery in distributed systems, you may wish to read A Note on Distributed Computing, at http://www.sunlabs.com/techrep/1994/abstract-29.html.

Write the implementation and server classes

At a minimum, a remote object implementation class must:

A "server" class in this context, is the class which has a main method that creates an instance of the remote object implementation, and binds that instance to a name in the RMI Registry. The class that contains this main method could be the implementation class itself, or another class entirely.

In this example, the main method is part of examples.hello.HelloImpl. The server program needs to:

More explanation follows the source for HelloImpl.java.

To create the file HelloImpl.java, which contains the code for the "Hello World" server, follow these steps:

  1. Right-click the project file Hello.jpx.
  2. Select Add Files/Packages.
  3. Enter HelloImpl.java in the File Name field.
  4. Click OK. A dialog asks if you want to create the file, click OK.
  5. Double-click the file HelloImpl.java. It displays, with the Source tab selected.

  6. Enter the following code in the Source window:
         package examples.hello;
         
         import java.rmi.Naming;
         import java.rmi.RemoteException;
         import java.rmi.RMISecurityManager;
         import java.rmi.server.UnicastRemoteObject;
         
         public class HelloImpl extends UnicastRemoteObject implements Hello {
         
             public HelloImpl() throws RemoteException {
              super();
             }
         
             public String sayHello() {
              return "Hello World!";
             }
         
             public static void main(String args[]) {
         
              // Create and install a security manager
              if (System.getSecurityManager() == null) {
                  System.setSecurityManager(new RMISecurityManager());
              }
         
              try {
                  HelloImpl obj = new HelloImpl();
         
                  // Bind this object instance to the name "HelloServer"
                  Naming.rebind("//localhost/HelloServer", obj);
         
                  System.out.println("HelloServer bound in registry");
              } catch (Exception e) {
                  System.out.println("HelloImpl err: " + e.getMessage());
                  e.printStackTrace();
              }
             }
         }
    
  7. Select File|Save All.

Implement a remote interface

In the Java programming language, when a class declares that it implements an interface, a contract is formed between the class and the compiler. By entering into this contract, the class is promising that it will provide method bodies, or definitions, for each of the method signatures declared in the interface that it is implementing. Interface methods are implicitly public and abstract, so if the implementation class doesn't fulfill it's contract, it becomes by definition an abstract class, and the compiler will point out this fact if the class was not declared abstract.

The implementation class in this example is examples.hello.HelloImpl. The implementation class declares which remote interface(s) it is implementing. Here is the HelloImpl class declaration:

     public class HelloImpl extends UnicastRemoteObject
         implements Hello {
As a convenience, the implementation class can extend a remote class, which in this example is java.rmi.server.UnicastRemoteObject. By extending UnicastRemoteObject the HelloImpl class can be used to create a remote object that:

Define the constructor for the remote object

The constructor for a remote class provides the same functionality as the constructor for a non-remote class: it initializes the variables of each newly created instance of the class, and returns an instance of the class to the program which called the constructor.

In addition, your remote object instance will need to be "exported". Exporting a remote object makes it available to accept incoming remote method requests by listening for incoming calls to the remote object on an anonymous port. When you extend java.rmi.server.UnicastRemoteObject or java.rmi.activation.Activatable, your class will be automatically exported upon creation.

If you choose to extend a remote object from any class other than UnicastRemoteObject or Activatable, you will need to explicitly export the remote object by calling either the UnicastRemoteObject.exportObject method or the Activatable.exportObject method from your class's constructor (or another initialization method, as appropriate).

Because the object export could potentially throw a java.rmi.RemoteException, you must define a constructor that throws a RemoteException, even if the constructor does nothing else. If you forget the constructor, compiling will produce the following error message:

     HelloImpl.java:13: Exception java.rmi.RemoteException must be caught, or it must be
        declared in the throws clause of this method. 
                super(); 
                     ^ 
        1 error
To review: The implementation class for a remote object needs to: Here is the constructor for the examples.hello.HelloImpl class:
    public HelloImpl() throws RemoteException { 
     super(); 
    }
Note the following: Although the call to the superclass's no-argument constructor, super(), occurs by default (even if omitted), it is included in this example to make clear the fact that the Java virtual machine (VM) constructs the superclass before the class.

Provide an implementation for each remote method

The implementation class for a remote object contains the code that implements each of the remote methods specified in the remote interface. For example, here is the implementation for the sayHello method, which returns the string "Hello World!" to the caller:
    public String sayHello() throws RemoteException {
     return "Hello World!";
    }
Arguments to, or return values from, remote methods can be any data type for the Java platform, including objects, as long as those objects implement the interface java.io.Serializable. Most of the core classes in java.lang and java.util implement the Serializable interface. In RMI: A class can define methods not specified in the remote interface, but those methods can only be invoked within the virtual machine running the service and cannot be invoked remotely.

Create and install a security manager

The main method of the server first needs to create and install a security manager: either the RMISecurityManager or one that you have defined yourself. For example:
if (System.getSecurityManager() == null) {
  System.setSecurityManager(new RMISecurityManager());
}
A security manager needs to be running so that it can guarantee that the classes that get loaded do not perform operations that they are not allowed to perform. If no security manager is specified, no class loading by RMI clients or servers is allowed, aside from what can be found in the local CLASSPATH.

Create one or more instances of a remote object

The main method of the server needs to create one or more instances of the remote object implementation which provides the service. For example:

HelloImpl obj = new HelloImpl();

The constructor exports the remote object, which means that once created, the remote object is ready to accept incoming calls.

Register the remote object

For a caller (client, peer, or applet) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.

For bootstrapping, the RMI system provides a remote object registry that allows you to bind a URL-formatted name of the form "//host/objectname" to the remote object, where objectname is a simple string name.

The RMI registry is a simple server-side name server that allows remote clients to get a reference to a remote object. It is typically used only to locate the first remote object an RMI client needs to talk to. Then that first object in turn, would provide application-specific support for finding other objects.

For example, the reference can be obtained as a parameter to, or a return value from, another remote method call. For a discussion on how this works, please take a look at Applying the Factory Pattern to RMI, at http://java.sun.com/j2se/1.3/docs/guide/rmi/Factory.html.

Once a remote object is registered on the server, callers can look up the object by name, obtain a remote object reference, and then remotely invoke methods on the object.

For example, the following code binds the name "HelloServer" to a reference for the remote object:

Naming.rebind("//localhost/HelloServer", obj);
Note the following about the arguments to the rebind method call: For security reasons, an application can bind or unbind only to a registry running on the same host. This prevents a client from removing or overwriting any of the entries in a server's remote registry. A lookup, however, can be done from any host.

Write a client program that uses the remote service

The applet part of the distributed "Hello World" example remotely invokes the HelloServer's sayHello method in order to get the string "Hello World!", which is displayed when the applet runs. To create the applet,
  1. Select File|New. Select the Applet icon from the New page of the Object Gallery. The Applet wizard displays.
  2. Change the Class value to HelloApplet.
  3. Change the Base Class to java.applet.Applet.
  4. Click Finish.

    The file HelloApplet.java displays in the Project pane.

  5. Enter the code that follows in bold to the appropriate place within the generated code:

    package examples.hello;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.applet.*;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    
    public class HelloApplet extends Applet {
      boolean isStandalone = false;
      String message = "blank";
      //"obj" is the identifier that we'll use to refer
      //to the remote object that implements the "Hello"
      //interface
      Hello obj = null;
    
      //Get a parameter value
      public String getParameter(String key, String def) {
        return isStandalone ? System.getProperty(key, def) :
          (getParameter(key) != null ? getParameter(key) : def);
      }
    
      //Construct the applet
      public HelloApplet() {
      }
    
      //Initialize the applet
      public void init() {
        try {
          jbInit();
        }
        catch(Exception e) {
          e.printStackTrace();
        }
      }
    
      //Component initialization
      private void jbInit() throws Exception {
        obj = (Hello)Naming.lookup("//" + getCodeBase().getHost()
          + "/HelloServer");
        message = obj.sayHello();
      }
    
      public void paint(Graphics g){
        g.drawString(message, 25, 50);
        }
    
      //Get Applet information
      public String getAppletInfo() {
        return "Applet Information";
      }
    
      //Get parameter info
      public String[][] getParameterInfo() {
        return null;
      }
    }
    
  6. Select File|Save All.

The applet does the following:

  1. First, the applet gets a reference to the remote object implementation (advertised as "HelloServer") from the server host's RMI Registry. Like the Naming.rebind method, the Naming.lookup method takes a URL-formatted java.lang.String. In this example, the applet constructs the URL string by using the getCodeBase method in conjunction with the getHost method. Naming.lookup takes care of the following tasks:

  2. The applet invokes the remote sayHello method on the server's remote object.

  3. The applet invokes the paint method, causing the string "Hello World!" to be displayed in the drawing area of the applet.
The constructed URL-string that is passed as a parameter to the Naming.lookup method must include the server's host name. Otherwise, the applet's lookup attempt will default to the client, and the AppletSecurityManager will throw an exception since the applet cannot access the local system, but is instead limited to communicating only with the applet's host.

Write the HTML file that contains the applet

The HTML file that contains the applet is generated in the previous step by the Applet wizard. The file is named HelloApplet.html. The generated code looks like this:

<HTML>
<head>
<title>Hello World</title>
</head>
<center> <h1>Hello World</h1> </center>
<body>examples.hello.HelloApplet will appear below in a Java enabled browser.

<applet
  CODEBASE = "."
  CODE     = "examples.hello.HelloApplet.class"
  NAME     = "TestApplet"
  WIDTH    = 400
  HEIGHT   = 300
  HSPACE   = 0
  VSPACE   = 0
  ALIGN    = middle
  >
</applet>
</body>
</HTML>

Note the following:

Write the policy file

In order to run this code on your system, you'll need to create a policy file in the directory on your system where you've installed the example source code.

Note: In this example, for simplicity, we will use a policy file that gives global permission to anyone from anywhere. Do not use this policy file in a production environment. For more information on how to properly open up permissions using a java.security.policy file, please refer to to the following documents:

To create the policy file,

  1. Right-click the project file Hello.jpx.
  2. Select Add Files/Packages.
  3. Enter policy.java in the File Name field.
  4. Click OK. A dialog asks if you want to create the file, click OK.
  5. Double-click the file policy.java. It displays, with the Source tab selected.

  6. Enter the following code in the Source window:

    grant {
      // Allow everything for now
     permission java.security.AllPermission;
    };
  7. Select File|Save All.


Compile and deploy class files and HTML files

The source code for the "Hello World" example is now complete and the project contains the following files:

In this section, you compile the .java source files to create .class files, stubs, and skeletons. A stub is a client-side proxy for a remote object, which forwards RMI calls to the server-side dispatcher (skeleton), which in turn forwards the call to the actual remote object implementation.

When you use the compiler, you must specify where the resulting class files should reside. For applets, all files should be in the applet's codebase directory. For our example, this directory is jbproject/examples/hello/classes. When the Applet wizard generates the HTML file, it puts the file in the correct location for your system setup.

Some Web servers, such as Java Web Server, allow accessing a user's public_html directory via an HTTP URL constructed as "http://host/~username/". If your Web server does not support this convention, you may use a file URL of the form "file://home/username/public_html".

Compile the Java source files

To compile the Java source files,

  1. Select File|Save All.

  2. Right-click on HelloImpl.java in the project pane. Select Properties from the context menu. Check the Generate RMI Stub/Skeleton field on the RMI/JNI tab of the Build page. Click OK.

  3. Select File|Save All.

  4. Right-click Hello.jpx, select Make.

These steps create the directory examples/hello (if it does not already exist) in the directory jbproject/examples/hello/classes. The command then writes to that directory the files Hello.class, HelloImpl.class, and HelloApplet.class. These are the remote interface, the implementation, and the applet respectively.

The stub and skeleton files are also generated to the same directory. These files are HelloImpl_Stub.class and HelloImpl_Skel.class .

The previous step is akin to running the javac and rmic compilers on the files.

If you need to produce stubs and skeletons that support access to the following, run rmic -vcompat from the command line.

  1. Unicast (not Activatable) remote objects from 1.1 clients and
  2. All types of remote objects from 1.2 clients

For an explanation of rmic options, refer to the Solaris rmic manual page at http://java.sun.com/j2se/1.3/docs/tooldocs/solaris/rmic.html, or the Win32 rmic manual page at http://java.sun.com/j2se/1.3/docs/tooldocs/win32/rmic.html.

The generated stub implements exactly the same set of remote interfaces as the remote object itself. This means that a client can use the Java language's built-in operators for casting and type checking. It also means that remote objects written for the Java platform support true object-oriented polymorphism.


Start the RMI registry, server, and applet

Start the RMI Bootstrap Registry

The RMI registry is a simple server-side name server that allows remote clients to get a reference to a remote object. It is typically used only to locate the first remote object an application needs to talk to. That object in turn will provide application-specific support for finding other objects.

Note: Before you start the RMI Registry, you must make sure that the shell or window in which you will run the registry either has no CLASSPATH set or has a CLASSPATH that does not include the path to any classes that you want downloaded to your client, including the stubs for your remote object implementation classes.

If you start the RMI Registry, and it can find your stub classes in its CLASSPATH, it will ignore the server's java.rmi.server.codebase property, and as a result, your client(s) will not be able to download the stub code for your remote object.

If JBuilder was installed to a directory other than the default directory (running Windows NT), you may need to modify your CLASSPATH by editing the System Environment properties from the Control Panel.

To start the registry on the server

  1. Select Tools|RMIRegistry.

This step produces no output and is typically run in the background.

You must stop and restart the registry any time you modify a remote interface or use modified/additional remote interfaces in a remote object implementation. Otherwise, the class bound in the registry will not match the modified class. To stop the registry,

  1. Unselect Tools|RMIRegistry.

Start the server

If you have problems running the example code, please take a look at the RMI and Serialization FAQ at http://java.sun.com/j2se/1.3/docs/guide/rmi/faq.html.

Note: A stub class is dynamically downloaded to a client's virtual machine only when the class is not already available locally and the java.rmi.server.codebase property has been set properly to where the class files live on the server. JBuilder takes care of setting the codebase property when the server is run. The following steps show how to start the HelloImpl server, specifying the java.security.policy properties:

  1. Right-click on the project file, Hello.jpx. Select Properties.
  2. Select the Run tab.
  3. In the VM Parameters field, enter the following string, substituting the actual location and name of the policy file you created earlier:

    -Djava.security.policy=<usr/home><username>/jbproject/examples/hello/policy.java 
    

    Note that there are no spaces from the "-D" all the way though.

  4. Select OK to close the dialog.
  5. Select File|Save All.
  6. Right-click HelloImpl.java, select Run.

The server is started. "HelloServer bound in registry" displays in the Message pane.

Run the applet

Once the registry and server are running, the applet can be run. An applet is run by loading its Web page into a browser or appletviewer. To view the application as an applet,

  1. Right-click HelloApplet.html.
  2. Select Web Run.

The Tomcat Web server starts, with the "Hello World!" applet running inside the Web View pane. If it is running correctly, you will see output similar to the following on your display:

This completes the tutorial.

For updates to this document, see http://www.javasoft.com/j2se/1.3/docs/guide/rmi/getstart.doc.html.

To learn more about Java RMI, start with these sites: