Novell Home

AnyInfo Example 6: Using RMI to Access a Database Service

Articles and Tips: article

LAWRENCE V. FISHER
Senior Research Engineer
Developer Information

01 Oct 1998


Describes how RMI is used in AnyInfo, the build process necessary to use RMI, and the steps to run the RMI application. Also presents the code listing and explanation for AnyInfo's RMI usage. Sixth example in a series.

Introduction

This article discusses the sixth example in the series introduced in the article, "The Transformation of API_Info into AnyInfo, a Multi-Tiered, Data Base Application," introduced in the July issue.

As shown in Figure 1, this example uses Java's Remote Method Invocation or (RMI) to access AnyInfo database services between tiers.

Figure 1: The AnyInfo example discussed in this article uses Java's Remote Method Invocation for remote interprocess communication between application tiers.

This article is divided into five parts:

  1. Part 1 briefly reviews the purpose and parts of AnyInfo.

  2. Part 2 describes a little about how RMI is used in AnyInfo.

  3. Part 3 describes the unique build process necessary to use RMI.

  4. Part 4 explains the steps to run the RMI application.

  5. Part 5 presents the code listing and explanation for AnyInfo's RMI usage.

Part 1: Brief Review of AnyInfo's Distributed Architecture

As shown in Figure 2, the AnyInfo application consists of four main pieces:

  1. The NDS administration snap-ins (currently implemented with the ConsoleOne snap-ins described in example 4 of this series).

  2. The client applet (described in example 2 of this series).

  3. The Client Proxy Application (described in example 5 of this series), which uses NDS directory information access to allow the user to select and connect to an AnyInfo database server somewhere on the network.

  4. The Access Proxy Application (to be described in example 7 of this series) which explains how to use JDBC to access an AnyInfo Oracle8 database.

Figure 2: Because AnyInfo uses Applets, there is no client installation or maintenace.

Examples 2, 4, 5, and 7 are written in Java. Each piece of code runs in a separate JVM. In this example, RMI is used to do interprocess communication. As explained in greater detail later, RMI is a distributed object technology allowing objects to communicate across machine and process boundaries.

Note: If you would like more information on the user interface and operation of the AnyInfo sample application, refer to Example 5 from the previous issue of this magazine.

The AnyInfo application was not intended to demonstrate GUI. Instead, it was developed primarily to show different ways to implement the plumbing between users and services in a multi-tiered network environment. The example in this issue demonstrates how to use RMI to access functionality between AnyInfo's tiers.

Part 2: Java's Remote Method Invocation (RMI), How Does It Work in AnyInfo?

In earlier AnyInfo examples, sockets were used to connect the different tiers of the application across the network. While this was effective, it forced us to modify our application design to accommodate the idiosyncrasies of the communications method. For example, AnyInfo optimized the use of sockets and streams by installing each request into a buffer along with the information that the server would need to process it. On the receiving end, the server had to unpack and interpret the buffer. Also, a protocol had to be established so that the receiving unit would know when a transmission was finished.

RMI allows us to virtually ignore the mechanics of network communication. With RMI, you can call a method in an object on another machine just as if it were in the same address space. For example, instead of packing and unpacking a buffer with a request to get a list of NDS trees on the network, by using RMI the AnyInfo client applet can now simply call the method on the server, String[] getTrees().

RMI manages the buffers and connections for you so that you can interact with remote objects (pass them as parameters, call their methods, and so on) as easily as you can interact with local objects.

Refer to the numbered circles in Figure 3 for the following discussion.

Figure 3: Follow the sequence of AnyInfo's RMI operations in the chart below.

  1. After the client applet has been downloaded, the first thing it does is obtain a reference to the remote client proxy object on the AnyInfo Client Proxy Server. In step 1 of the AnyInfo Client Applet, you can see that a static method called lookup( ) from a java.rmi class called Naming is passed a URL string. This URL is composed of the name of the server hosting the remote object and the arbitrary name given to the remote object by the programmer.

    During the lookup, RMI uses the URL to connect to the RMI name registry on the appropriate server. If RMI on the server can locate the specified remote object name (in this case "cliProxyObj"), it will return an initialized remote object reference to the requesting applet.

  2. After the AnyInfo applet has initialized, the user is shown an uninitialized database query dialog. The dialog has controls that allow the user to select an NDS tree and then an AnyInfo database on the tree. When the user clicks the dialog's "Select Tree" button, the applet will call the remote client proxy object's getTreeList( ) method to obtain a Vector containing names of all of the NDS trees on the network.

    Note: For info on how to access NDS with your program, refer to example 5 in the previous issue of this magazine.

    As you can see in the Figure 3, the remote object reference obtained by the RMI name lookup is used to instantiate a local stub object in the client's JVM. When the client's applet references methods via the remote reference, the local stub packs up or marshals the parameters and communicates them across the network to the remote object's skeleton on the server. The skeleton unpacks or unmarshals the parameters for the remote object on the server. The same process happens in reverse for return values.

    This transparent interaction between stubs and skeletons enables clients to access the remote object as though is were a local object reference.

    Notice that the method accessed in the remote object is declared in the ClientProxy interface. All remote objects must implement an interface declaring their exposed methods. Clients using a remote object are then limited to the methods declared in its interface.

  3. After the user has selected the desired NDS tree from the list, the applet will access the tree through the client proxy application to obtain information about its AnyInfo databases. The return value from the remote getTreeDatabaseInfo( ) method will be a three element object array. The first element will be a Vector of database subject names (a Vector is like a list), the second will be a corresponding Vector of IP addresses, and the third a Vector of operator names. The applet installs the database subject names into a viewable List and presents it to the user.

    Note: The code for the getTreeList( ) and getTreeDatabaseInfo( ) methods can be found in the September issue of this magazine where usage of Novell's NDS NWDir Java bean to access NDS directory information, is discussed.

  4. After the user selects a database subject name from the database list, its corresponding IP address is obtained from the IP address Vector and passed to the remote object's getDBAccessProxy( ). The purpose of the getDBAccessProxy( ) method is to obtain a reference to the remote object fronting the database.

    You might ask yourself at this point, why go through the client proxy to obtain a reference to the database object? After all, the applet already has the Database server's IP address. Wouldn't it be more efficient for the applet to go directly to the Database Server?

    The reason the applet cannot interact with the Database server is that its AppletSecurityManager monitors all of its Input/Output operations to make sure that nothing is written out or loaded into the applet from an untrusted source. If the AppletSecurityManager detects a connection to any server other than the one that is supplying the applet's classes, it will throw an exception to terminate the operation. This ensures that the applet plays in its own sandbox, but it makes it more difficult for us to construct a distributed network application. So, AnyInfo uses its client proxy as a broker to communicate with the remote database.

  5. The Naming.lookup( ) call in the getDBAccessProxy( ) method connects to the registry on the server specified by the applet supplied IP address to obtain a remote reference to the desired database access object.

  6. Sending the database object's remote reference back to the client applet would cause the applet's AppletSecurityManager to throw an exception. This is because the applet would attempt to establish a connection to the database server so that it can load the stub file for the remote object (RMI stubs are explained later). As mentioned earlier, this violates the AppletSecurityManager's rule that nothing can be loaded from any server other than the one that supplies the applet's classes. So, to overcome the applet's security issue, AnyInfo creates a wrapper for the database object.

    As you can see in step 6 of the dBWrapper object, the wrapper object's constructor initializes it to hold a reference to the remote database object. Also notice that the wrapper implements the same interface as the database object.

    Since the wrapper is hosted by the server providing the applet's classes, when its remote reference is returned to the applet, the applet's security manager remains happy.

  7. After the user has selected a database and the applet has obtained a remote reference to it, the user can interact with the database. Since this example still implements the AnyInfo database with the legacy flat-file database service API_Info, all communication with the database is accomplished through buffers. All database request buffers are sent to the client proxy's dispatchRequest( ) method along with the remote reference to the database wrapper object.

  8. In the client proxy, dispatchRequest( ) simply forwards the request to the wrapper's accessDB( ) method.

  9. The accessDB( ) method in the database wrapper object calls the accessDB( ) method in the real remote database access object, dBObj on the Database server.

  10. In this example, the real accessDB( ) method in the real database access object, accesses a Java/NLM gateway NLM called RMIGate.nlm. This allows it to access AnyInfo's legacy service NLM, API_Info. The AnyInfo example in the next issue will demonstrate how to use JDBC to access an Oracle8 database on AnyInfo's Database server.

Part 3: Building RMI Distributed Applications

RMI makes network communications mostly transparent to the developer, but it does require some extra steps in building and running programs.

Shown in Figure 4 are the major pieces of an RMI program that you need to be concerned with for the purpose of this discussion. During this discussion, consider "Java RMI" to be a black box that takes care of making the remote calls happen without you having to worry about it. Also, the client shown is an applet, but it can be any Java executable.

Clients interface with RMI through a chunk of code called a stub. Servers interface with RMI through skeletons. Stubs and skeletons are program-specific, but don't worry, you don't have to write them. They are generated with a utility called rmic acting upon the class or classes containing the implementation of your remote object's interface methods. During program operation, RMI transparently loads the stubs and skeletons so that the client and server can communicate. This means that it is very important that the platforms hosting the client and server know where to find the stub and skeleton.

Figure 4: RMI clients and servers communicate through stubs and skeletons.

Note: The rmic utility is part of the Java developers kit available at: http://java.sun.com/products/jdk

As shown in Figure 5, building an RMI program consists of five steps:

  1. Write RMI interfaces to describe the methods you want exposed in your remote object.

  2. Implement the interfaces in RMI server sources and call the specified methods as necessary in the client sources.

  3. Compile the client and server sources into class files.

  4. Run rmic on the server class which implements your RMI interface methods to generate the stub and skeleton for the remote object. On Windows, the rmic utility is an .exe. If your system's PATH variable is set up to access the Java Developer Kit's bin directory (as it would have to be if you were using javac), then simply open a DOS window and cd to the location of the server's class file where you can call rmic on it. Remember that you must specify the class file's fully qualified, case-sensitive name (including package names delimited with the `.' character) to rmic.

  5. Finally, put all of the classes (stubs and skeletons are classes, too) in the right place.

Figure 5: The process shown below is specifically tailored for RMI applications whose clients are applets.

As you can see in Figure 5, the stub is put into the "client applet's codebase."

The server classes on the other hand go into the directory specified by server's RMI codebase. On the Netware server, you can set the server's RMI codebase by entering the following command at the server's monitor:

java -Djava.rmi.server.codebase=http://[MyServerName]/   [ClassContainingMain]

Notes:

  1. The command above will attempt to load the class files for the server program. The program may contain code to attempt to bind with a registry. If this code is run before the registry is running the program will fail with a class not found exception as it tries to access the missing registry. (See "Running RMI Distributed Applications" below.)

  2. The [ClassContainingMain] value must be the fully qualified name (including package names) of the class in the application which contains the main( ) routine and must take up where one of the CLASSPATHs leaves off so that RMI can locate your class files.

  3. The `/' character trailing the server name is necessary.

Part 4: Running RMI Distributed Applications

There are two main steps to running the RMI application.

  1. Launch the rmiregistry on each server hosting RMI server objects.

  2. Load the server program. If your client is an applet, users access it via their browser, so you don't need to load it. If your client is an application, then you have to load it. Make sure that your stub is in a place that can be found with your CLASSPATH setting.

Accessing the RMI Registry

RMI server applications use the RMI registry on the server to expose their remote objects with the arbitrary service names specified by their programmers.

Once a client has one reference to a remote object, it can be used to obtain others without having to access an RMI registry (you saw this earlier in the "AnyInfo RMI Discussion Chart"). However, to get that first remote object, the client will have to look it up from the naming service on the hosting server. So, RMI servers need to bind their primary remote objects into the registry so that clients can find them.

If your server is not already running an RMI name registry, you must either start one up programmatically with the Java.rmi's LocateRegistry class and its static createRegistry( int portID ) method or after you have already loaded Java, you can enter a command at the monitor like the one below:

rmiregistry

Note: The default port for the RMI registry is 1099. If your server needs that port for something else, you can specify another port number when you start up the registry. However, then the clients will have to know the unique port number as well.

Loading the Server Program

If your server program performs a registry bind (most will perform at least one), and you attempt to load it before you load the registry, your program will throw a class not found exception as it attempts to find the registry. So, load your server application after you have loaded the registry.

In order to add a remote object to the registry, your server program must get a reference to the registry using one of java.rmi LocateRegistry's static getRegistry( ) methods and then add the remote object and its name to the registry. The Naming.rebind( ) method handles this automatically. This is described later in the code explanation.

Clients must already know the domain name or IP address of the server hosting the registry and the registry's port number in order to access the registry to obtain a remote object. An applet already knows the connection information about its server and so can easily obtain the server's name or IP using java.net's InetAddress class methods. This is also described later in the code explanation.

Part 5: Finally, the Source Code Listing and Explanation

Below is a source code listing for AnyInfo's RMI. The large italic, double-underlined numbers refer to explanations after the code listing.

1

--------------------------------------------------------------------------------

/********************************************

    INTERFACE ClientProxy is the interface used for the remote object representing the

    RMIAnyInfoClientProxy application running on AnyInfo's client proxy server.

********************************************/

    package RMIAnyInfoClasses;

    import java.util.*;

    public interface ClientProxy extends java.rmi.Remote

    { public Vector getTreeList( )

                    throws java.rmi.RemoteException;

       public Object[] getTreeDatabaseInfo( String treeName )

                    throws java.rmi.RemoteException;

       public DBAccessProxy getDBAccessProxy( String dbAccessIPAddress )

                    throws java.rmi.RemoteException;

       public String dispatchRequest(DBAccessProxy rDBAccessObj, String request)

                    throws java.rmi.RemoteException;

       }



2

--------------------------------------------------------------------------------

/********************************************

    INTERFACE DBAccessProxy is the interface used for the remote object representing the

    RMIDBAccess application running on database servers

********************************************/

    package RMIAnyInfoClasses;

    public interface DBAccessProxy extends java.rmi.Remote

    { String accessDB(String request) throws java.rmi.RemoteException;  }





    /****************************************************************

    CLASS AnyInfoCl contributes all RMI functionality to the client applet

    ****************************************************************/

    package RMIAnyInfoClasses;

    import java.rmi.*;

    import java.io.*;

    import java.util.*;

    import java.applet.*;



    public class AnyInfoCl extends Applet

    {protected static String treeName      = null;

    protected static String databaseName   = null;

    protected static String dbOperatorDName= null;



    public AnyInfoRMIAppletGUI mainW ;

    private ClientProxy rClientProxyObj;// remote obj reference for client proxy

    private DBAccessProxy   rDBAccessProxyObj;//remote obj reference for database proxy



    public void init()

    { setRemoteClientProxyObject ();// obtain reference to client proxy remote obj

        mainW = new AnyInfoRMIAppletGUI( this );

    }

3

--------------------------------------------------------------------------------

    public void setRemoteClientProxyObject ()   // called by applet's GUI

    { URL hostURL =  getCodeBase();

       String hostName = hostURL.getHost();

       try

       { InetAddress hostAddress = InetAddress.getByName(hostName);

           String clientProxyIPAddress = hostAddress.getHostAddress();

           String lookUpString = "rmi://"+clientProxyIPAddress+"/AnyInfoClientProxy";

           rClientProxyObj = (ClientProxy)Naming.lookup(lookUpString);

       } catch (Exception e) {   fail ("setRemoteClientProxyObject: ", e );  }

    }

4

--------------------------------------------------------------------------------

    public Vector requestTreeListFromClientProxy ( )    // called by applet's GUI

    { Vector reply = null;

       try {   reply = rClientProxyObj.getTreeList(); }

       catch (Exception e) {   fail ("requestTreeListFromClientProxy: ", e );  }

       return ( reply );

    }

5

--------------------------------------------------------------------------------

    public Object[] requestDatabaseInfoFromClientProxy ( )  // called by GUI

    { Object[] reply = null;

       try {   reply = rClientProxyObj.getTreeDatabaseInfo( treeName ); }

       catch (Exception e) {   fail ("requestDatabaseInfoFromClientProxy: ", e );}

       return ( reply );

    }

6

--------------------------------------------------------------------------------

    public void setRemoteDBAccessObject ( String dbAccessIPAddress )//called by GUI

    { try { rDBAccessProxyObj=rClientProxyObj.getDBAccessProxy( dbAccessIPAddress );}

       catch (Exception e) {   fail ("setRemoteDBAccessObject: ", e );  }

    }

7

--------------------------------------------------------------------------------

    public String sendDBRequest2DBAccessProxy ( String request ) // called by GUI

    { String reply = null;

       try {   reply = rClientProxyObj.dispatchRequest(rDBAccessProxyObj,request);}

       catch (Exception e) {   fail ("sendDBRequest2DBAccessProxy: ", e );  }

       return ( reply );

    }

    private void fail (String methodName, Exception e )

    { System.out.println(methodName +e.getMessage());  e.printStackTrace();  }

8

--------------------------------------------------------------------------------

    finally

    { rClientProxyObj = rDBAccessProxyObj = null;

        System.runFinalization();

        System.gc();

     }

}





/********************************************

CLASS RMIAnyInfoClientProxy supplies all remote objects to the client applet.

********************************************/

package RMIAnyInfoClasses;

import java.io.*;

import java.net.*;

import java.util.*;

import com.novell.beans.NWDir.*;

import com.novell.beans.util.NWBeanException;

import java.rmi.*;

import java.rmi.server.UnicastRemoteObject;



public class RMIAnyInfoClientProxy extends UnicastRemoteObject

            implements RMIAnyInfoClasses.ClientProxy

9

--------------------------------------------------------------------------------

{   public RMIAnyInfoClientProxy ( ) throws RemoteException  {   super();  }

        10

        --------------------------------------------------------------------------------

    public static void main ( String[] args )

    {//setup RMI to use the current local IP address

        InetAddress thisInetaddress = null;

        try

        { thisInetaddress = InetAddress.getLocalHost();

            String thisIPAddress = thisInetaddress.getHostAddress();

            Properties currentProperties = System.getProperties();

            currentProperties.put("java.rmi.server.hostname", thisIPAddress );

            System.setProperties(currentProperties);



            // Create and install a security manager

            System.setSecurityManager(new RMISecurityManager());



            RMIAnyInfoClientProxy remoteObj = new RMIAnyInfoClientProxy( );

            Naming.rebind("//"+thisIPAddress+"/AnyInfoClientProxy", remoteObj );

        }

        catch( java.net.UnknownHostException e ){    fail ( e  );    }

        catch (Exception e )            {    fail ( e  );    }

    }

    public static void fail ( Exception e  )

    { System.out.println("RMIAnyInfoClientProxy err: " + e.getMessage());

        e.printStackTrace();

        System.exit ( 1 );

    }



11

--------------------------------------------------------------------------------

    public DBAccessProxy getDBAccessProxy (  String dbAccessIPAddress )

                          throws java.rmi.RemoteException

    { DBAccessProxy rDBAccessProxyObj= null;

        try

        { String lookUpString = "rmi://" + dbAccessIPAddress +

                    "/AnyInfoDBAccessProxy";

            rDBAccessProxyObj = (DBAccessProxy)Naming.lookup(lookUpString);

            rDBAccessProxyObj = new DBAccessProxyWrapper(rDBAccessProxyObj);

        }

        catch (Exception e)

        { System.out.println("setRemoteClientProxyObject:"+e.getMessage());

            e.printStackTrace();

        }

        return (rDBAccessProxyObj );

        }

12

--------------------------------------------------------------------------------

    public String dispatchRequest ( RMIAnyInfoClasses.DBAccessProxy rDBAccessObj,

                String requestData ) throws java.rmi.RemoteException

    { return  rDBAccessObj.accessDB( requestData );//Forward request to database }



    /*The rest of the client proxy methods required to implement ClientProxy

        [getTreeList( ) & getTreeDatabaseInfo( )] use Novell's NWDir bean to access an

        NDS directory to obtain info about AnyInfo databases available on the network.

        These methods were shown & explained in the previous issue of this magazine. */

    }





    /****************************************************

    CLASS RMIDBAccessProxy is the AnyInfo database access application.

    *****************************************************/

    package RMIAnyInfoClasses;

    import java.io.*;

    import java.net.*;

    import java.util.*;

    import java.rmi.*;

    import java.rmi.server.UnicastRemoteObject;



    public class RMIDBAccessProxy

                extends UnicastRemoteObject implements RMIAnyInfoClasses.DBAccessProxy

    {

    public native String RMIGate( int request , String reqData );

    static  {   System.loadLibrary("RMIGate");  }

    static RMIDBAccessProxy rObject;

    public static final String SRVR_ERROR_STR       = "ERROR:" ;

        9

        --------------------------------------------------------------------------------

    public RMIDBAccessProxy ( ) throws RemoteException  {   super();    }



10

--------------------------------------------------------------------------------

    public static void main ( String[] args )

    {// setup RMI to use the current local address

        InetAddress thisInetaddress = null;

        try { thisInetaddress = InetAddress.getLocalHost(); }

        catch( java.net.UnknownHostException e ) { e.printStackTrace();  }

        String thisIPAddress = thisInetaddress.getHostAddress();

        Properties currentProperties = System.getProperties();

        currentProperties.put("java.rmi.server.hostname", thisIPAddress);

        System.setProperties(currentProperties);



        // Create and install a security manager

        System.setSecurityManager(new RMISecurityManager());

        try

        { RMIDBAccessProxy remoteObj = new RMIDBAccessProxy( );

            Naming.rebind("//"+thisIPAddress+"/AnyInfoDBAccessProxy", remoteObj);

        } catch (Exception e) { fail("main",e); }

    }



    public static void fail ( String methodName, Exception e  )

    { System.out.println("RMIDBAccessProxy."+ methodName +e.getMessage());

        e.printStackTrace();

        System.exit ( 1 );

    }



13

--------------------------------------------------------------------------------

    public String accessDB( String requestData) throws RemoteException

    { String replyData = "";

        if( requestData.equals("") || requestData.length() < 2 )

            replyData = SRVR_ERROR_STR+"No valid request code" ;

        else

        { try

            { char digChar = requestData.charAt(0);

                if(! Character.isDigit(digChar))

                    replyData = SRVR_ERROR_STR+"No valid request code" ;

                else

                { String reqStr = requestData.substring(0,1);

                    try

                    { int request = Integer.parseInt(reqStr);

                        String reqData = requestData.substring(2);

                        replyData = RMIGate( request, reqData );

                    }

                    catch( NumberFormatException c)

                    {   replyData = SRVR_ERROR_STR+"No valid request code" ;    }

                }

            }

                atch(StringIndexOutOfBoundsException b)

            {   replyData = SRVR_ERROR_STR+"No valid request code" ;    }

        }

        return ( replyData);

    }

}

AnyInfo's RMI Source Code Explanation

  1. The ClientProxy interface defines the exposed methods in the remote ClientProxy object. A remote reference to this object will be held by all client applets running in the system. All interaction between the applet and the network takes place through this object.

    Notice that the ClientProxy interface extends java.rmi.Remote. All interfaces used for remote objects must extend Remote. Remote simply serves to identify the remote object as remote, it contains no methods.

    Also notice that each method listed in the interface throws a java.rmi.RemoteException. This is a requirement as well. All remotely invokable methods must throw this exception because of the many problems that can occur between network entities.

  2. The DBAccessProxy interface defines a remote object that will be instantiated on every AnyInfo database server in the system. AnyInfo's Client Proxy server will instantiate these objects as they are selected and accessed by the client applets. The accessDB( ) method defined in DBAccessProxy is used to forward database requests from the applets to the API_Info NLM and its flat-file database. Later examples of AnyInfo will use JDBC to access a small Oracle8 database.

  3. The purpose of the applet's setRemoteClientProxyObject( ) method is to obtain a remote reference to the ClientProxy object on the ClientProxy server.

    First the applet obtains the IP address of the server. This is possible because the server hosting the ClientProxy object must always be the same as the server hosting the applet's class files. So, the browser running the applet already has connection information for the server. As shown in the source, the applet can easily access this information with java.net's InetAddress methods.

    Next a URL string is formed that will look something like this:

    rmi://123.12.321.123/AnyInfoClientProxy

    RMI will use the IP address to locate the RMI name registry service on the server at "123.12.321.123" on port 1099, the default port for RMI. It will then attempt to have the registry look up a remote object named "AnyInfoClientProxy". If an object reference is registered with that name the RMIClassLoader will attempt to locate the remote object's stub class. First it will look inside of the remote reference to see if it has an embedded URL describing where to find the stub. If the URL is missing or the stub is not at the URL specified location, the loader will look in the location specified by the applet's codebase (the location of its classes). If the stub is not there, the RMIClassLoader will throw an exception. If it is there, the stub is loaded and returned to the applet along with the remote reference. The applet assigns the remote reference to the rClientProxyObj variable.

  4. The ClientProxy's getTreeList( ) method uses Novell's NWDir bean to lookup all of the NDS trees listed in the server's SAP table. The applet will put the trees into a list and present it to the user so that one may be selected.

  5. AnyInfo NDS objects were designed to contain connection information for AnyInfo database servers. The ClientProxy's getTreeDatabaseInfo( ) method uses Novell's NWDir bean to read this database connection information from all of the AnyInfo objects in a selected tree. The applet will put the database subject names into a list and present it to the user so that one may be selected.

  6. The ClientProxy's getDBAccessProxy( ) method receives an IP address for a selected database server and uses it to obtain a remote reference to a DBAccessProxy object. This reference is then installed into a DBAccessWrapper object instantiated on the ClientProxy server. A remote reference to the DBAccessWrapper object is passed back to the applet. The AppletSecurityManager accepts the wrapper object because it was provided by the applet's host. The original DBAccessProxy object reference would not have been acceptable to the AppletSecurity manager because it was hosted on a server different than the applet's host.

  7. The ClientProxy's dispatchRequest( ) method receives the remote reference to a DBAccessProxyWrapper object held by the applet. The dispatchRequest( ) method will invoke the accessDB( ) method in the wrapper which will then invoke the accessDB( ) method in the remote reference to another DBAccessProxy object residing on the target Database server. This indirection is necessary because the applet's AppletSecurityManager would throw an exception if the applet attempted a connection to the remote Database server because it was not the source of its applet classes.

  8. Like local Java objects, remote objects are garbage collected when it is determined that there are no longer any references to them. However, as you would imagine, keeping track of references to a remote object across a network is a much more difficult task.

    RMI uses some very sophisticated reference counting to determine whether or not a remote object should be collected. First, local virtual machines maintain their own reference count for remote objects in their environments. When a local virtual machine determines that there are no more references to a remote object, it will send an unreferenced message to notify the object's server of its zero reference count. The server attempts to keep track of all of the distributed VMs which have received references to the object. When it has received unreferenced messages from all of them, it garbage collects the object.

    On the client applets, the finally method will be called by the System just before the AnyInfoCl object is deleted. AnyInfoCl's finally( ) method makes sure that the remote references in the applet are set to null and then calls the System garbage collector to make sure that the unreferenced message is sent to the hosting server in a timely manner.

  9. The default constructor for all Java object's is one without arguments. The programmer need not explicitly call the default constructor for an object's superclass because Java will do it for him whether or not it is shown in the program's source. The reason the superclass's default constructor is explicitly shown in the RMIAnyInfoClientProxy constructor is to remind you that it is there so that we can explain why the constructors of all subclasses of UnicastRemoteObject must throw a RemoteException.

    The default constructor for UnicastRemoteObject installs a listener at the lower transport levels of RMI to listen for incoming calls to the remote object. If transport resources, (for example, TCP) are not available for some reason, UnicastRemoteObject will throw the aforementioned RemoteException.

  10. The first thing the main routine does is to set the Java System property java.rmi.server.hostname to the IP address of the local host server. The IP address is used for the host name instead of a domain name for two reasons:

    • So that the application can be moved to different servers without recompiling.

    • The server may not have a domain name listed on a domain name server.

    Next, main( ) installs the RMISecurityManager which must be done before a reference to a remote object can be obtained. This is because the RMISecurityManager monitors the loading of remote stubs and RMI classes for applications much the same way as the AppletSecurityManager does for applets. If there is no security manager then RMI will load stubs only from the local file system as defined by the server's classpath and java.rmi.server.codebase settings.

    Note: If you are worried about evil remote stubs and classes, then set System property CodebaseOnly to true and load stubs and classes only from the local file system as defined by the server's classpath and java.rmi.server.codebase settings.

  11. getDBAccessProxy () is called by the client applet to obtain a remote reference to a DBAccess object. The client proxy obtains the reference for the applet because the applet's security manager will not allow it to connect to any other server but the one supplying the applet's classes. Since the client proxy is an application, it is exempt from such restrictions and so can connect to any server it wants.

    Since getDBAccessProxy( ) is the first remote method we discuss, we should mention that when an RMI client calls a method in a remote object, the remote RMI runtime serving the object, may or may not execute the call in a separate thread. While the thread of execution in the client's local virtual machine blocks on the call, another thread in the same VM might also make the same call. In this case, both calls may end up executing in the same thread on the server. This could cause concurrency problems. So, exported methods should be made thread safe.

    Note: Java guarantees that remote calls from different VMs will get separate threads of execution.

    getDBAccessProxy( ) performs the same kind of lookup that was discussed in discussion point 3 above except that the DBAccess service name, "AnyInfoAccessProxy" is used with the IP address provided by the applet.

  12. The Client Proxy's dispatchRequest( ) method is called by the client applet to forward a client applet's database request to the appropriate RMIDBAccessProxy server.

  13. On the Database server, the RMIDBAccessProxy object's accessDB( ) method is called by a DBProxyWrapper object representing a client applet on the Client Proxy server. In this example, accessDB( ) will pass the request on to a primitive flat-file database inherited from previous examples. Later examples of AnyInfo use JDBC to communicate with an Oracle8 database instead.

    The accessDB( ) method accesses the flat-file database through RMIGate.nlm running on the same machine. RMIGate is the Java native method gateway used by this example application to access AnyInfo's legacy service NLM, API_Info.nlm. API_Info.nlm will process the request and send its response back through the gateway to this method which will return it to the Client Proxy which will return it to the user running the AnyInfo Client Applet in his browser.

    Note: For more information on how to interface Java with NLMs (native method gateways), refer to Example 2 of this series.

Conclusion

In this article we implemented an RMI scheme that made programming a distributed application look very much as if it were in a single address space. RMI is very powerful and yet simple.

AnyInfo has come a long way. However, we still have the API_Info database service inherited from previous examples. In later examples, we will replace this service with an Oracle8 database accessed by AnyInfo through JDBC. And still later AnyInfo will regain access to the legacy API_Info service using CORBA.

For more information about other examples in this series, refer to: http://developer.novell.com/education.

To download the project corresponding to this article, refer to: http://www.novell.com/coolsolutions/tools/15619.html.


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© 2014 Novell