< Day Day Up > |
Remote Method Invocation (RMI) forms the basis of the robust architecture behind the Enterprise JavaBeans, which is the most popular Enterprise Java development framework. In this section, the concepts of RMI are discussed in great detail in order to lay the foundation necessary for future topics. To use the RMI framework for creating distributed server objects, the first step is to define an interface (usually known as remote interface) that extends the java.rmi.Remote interface. The server object that should be accessible to the client remotely should implement this (application-specific) remote interface and also extend the java.rmi.server.UnicastRemoteObject in order to provide the remote behavior to the server object. Every method of the server object that should be accessible to the client should be defined in the remote interface and be implemented in the server object. Through the remote interface, the RMI framework exposes the methods implemented by the server object. Although the remote interface is compiled using the Java compiler (javac program), the server object is compiled using the RMI compiler (rmic program). This process creates a stub class file along with the class file for the server object implementing the interface. The stub class file should be made available to the client application, while the server object contains the corresponding skeleton code, which works with the client-side stub code in marshalling and unmarshalling the data objects and is instantiated on the server side. Until the release of Java 2 SDK, the rmic compiler created two files, the client-side stub class and the server-side skeleton class. Since the release of Java 2 SDK, the rmic stopped creating the server-side skeleton class because the server-side marshalling/unmarshalling is handled by the server object itself. Even while using the Java 2 SDK version of the rmic compiler, the users can explicitly specify which version of Java runtime the RMI object should be compiled using the –v<version> option as shown here. When Version 1.1 is specified, both the stub and skeleton classes are generated (which are compatible with Java 1.1 Release), and when Version 1.2 is specified, only the stub class is generated (which is compatible with Java 1.2 or Java 2 Release). From the rmic command line, it should be noted that the Java source file extension .java is not specified after the source file name, and it should also be noted that only those objects that implement RMI-based interfaces need to be compiled using rmic while all the other classes are compiled using javac.
$ rmic –v1.1 <RMIObjectImplementationClass> $ rmic –v1.2 <RMIObjectImplementationClass>
Under the hood, the RMI framework utilizes the TCP/IP networking layer installed on the operating system to perform the necessary communication between the client and server programs and implements its own JRMP (Java Remote Method Protocol) over TCP/IP. It should be noted that because Java is designed to be a platform-independent architecture, the Java API and the associated Java Virtual Machine take the responsibility for handling the differences in the implementation of TCP/IP layer on different operating systems and architectures and providing a unique programming interface to the developers. As specified in an earlier chapter, this is the beauty of this platform-independent architecture. Starting from Java 2 SDK Version 1.3, the RMI framework also implements the IIOP (Internet Inter-ORB Protocol)-based data marshalling mechanism, on the same lines of the CORBA architecture. These topics are explained in detail in the next section.
Because for every method defined in the server object there is a corresponding method in the stub class with the same name, the stub class acts as a proxy and—for all practical purposes—represents the server object on the client side. It is the RMI framework that performs the data marshalling needed between the stub class and the server object, using object serialization. There is one more piece needed for the whole thing to work together; it is the rmiregistry, with which the server object registers itself. Rmiregistry is a separate console program and is part of the Java 2 SDK and Java 2 runtime library. It should be executed in a separate console window. The server program instantiates the remote object and registers it with the rmiregistry. Figure 7.2 displays the sequence of events while using the RMI object from a client application.
From the figure, the sequence of steps involved to enable a client application to access the RMI server object are identified and listed here.
The RMI registry (rmiregistry) is started (step 1).
The RMI server application containing the RMI object is started (step 2).
The server application instantiates the server object and registers it with the RMI registry (step 3), providing a name to the object.
The client application inquires the naming service with the name of the RMI object (step 4).
The naming service interacts with the RMI registry for the specified RMI object (step 5).
After successful identification of the RMI object, the naming service returns a reference to the remote interface of the RMI object to the client application (step 6).
The client invokes a method on the remote interface, which triggers the corresponding method on the associated stub object (step 7).
The stub object marshals the method parameters to the corresponding skeleton, which is embedded within the RMI object using the RMI framework (step 8), and unmarshals and returns the result of execution to the client application (step 9).
Now let us go through the process of creating and using a RMI object. As specified earlier, the first step is to create an interface that extends the Remote interface defined in the java.rmi package. The Remote interface is a tagging interface and therefore does not contain any methods to be implemented, very similar to the Serializable interface discussed in an earlier section. The newly inherited interface should contain all the methods that should be implemented by the RMI object. Every method defined in this interface is a remote method and throws the RemoteException defined in the java.rmi package. The RMI client-server model is demonstrated using a simple example where the server implements a method that will return the current system time on the server as a java.util.Calendar object, a method that returns the same information as a String object, and a method to set the server system time with a java.util.Calendar object as argument. Listing 7.10 displays the remote interface definition for the RMI object. This listing is available on the accompanying CD-ROM.
After defining the remote interface, it should be compiled—using the normal javac compiler with the syntax similar to the one shown here—to produce the .class file for the interface.
$ javac CurrTime.java
Now we should implement the methods defined in this interface in the server object. Listing 7.11 displays the server object implementation. From the listing, it should be noted that the server object extends the java.rmi.server.UnicastRemoteObject class and implements the interface defined in Listing 7.10. After successful instantiation of the RMI server object, it should be registered with the rmiregistry, using the naming service provided by the java.rmi.Naming class. This is also called binding the RMI object with the registry. Only objects registered with the rmiregistry will be accessible to clients via the RMI framework. This can be done in two ways: the server main program that instantiates the remote server object can do this binding, or the remote server object can bind itself with the rmiregistry in its constructor. Whichever method is used to bind the remote object, it should be provided with a name, so that the rmiregistry identifies the object by name. In the current example, the RMI server object registers itself with the rmiregistry in its constructor method. At this point, it is very important not to confuse the rmiregistry with the concept of registry implemented on the Microsoft Windows operating systems. The rmiregistry is a console application running within a Java Virtual Machine environment and maintains the names of remote objects within its internal memory, whereas the Microsoft Windows registry is a sort of database of information pertaining to all the applications installed on the computer (and additionally COM/ActiveX related information) and stored to the disk. The rmiregistry does not have any such overhead. It is a very simple Java program. Listing 7.11 is available on the accompanying CD-ROM.
import java.util.*; import java.rmi.*; import java.rmi.server.*; public class CurrTimeImpl extends UnicastRemoteObject implements CurrTime { Calendar currentTime; public CurrTimeImpl(String objectName) throws RemoteException { super(); try { currentTime = Calendar.getInstance(); Naming.rebind(objectName, this); } catch (Exception e) { if (e instanceof RemoteException) throw (RemoteException)e; else throw new RemoteException(e.getMessage()); } } public Calendar getCurrentTime() throws RemoteException { return currentTime; } public String getCurrentTimeString() throws RemoteException { return currentTime.getTime().toString(); } public void setCurrentTime(Calendar currTime) throws RemoteException { currentTime = currTime; } }
The server object should be compiled with the rmic compiler in order to produce the stub class file for the client, in addition to producing the usual .class file for the implementing class, with syntax similar to the one shown here. From the command line, it should be noted that the .java file extension is omitted while compiling the server object using the rmic compiler, and the –v1.2 option is specified, as the demo application is built for Java 2 (J2SDK Version 1.3 and above) framework.
$ rmic –v1.2 <CurrTimeImpl>
Once the server object is implemented, the next step is to implement the main program that instantiates the RMI server object and makes it available to the clients and a client application that will make method calls to the RMI server object. Listing 7.12 displays the server main program, which first initiates the RMI security manager, creates an instance of the remote server object, and finally waits in an infinite loop so that the remote object is available to the clients. To make the demonstration simple, it is coded this way. In a typical Enterprise-level application, the server might spawn a separate thread to provide client services while the thread waits in an infinite loop. The infinite loop may be broken when the thread is interrupted manually or by an external process, or internally by examining a state variable of the thread, which is updated by an external process through the remote object mechanism. Listing 7.12 is available on the accompanying CD-ROM.
import java.rmi.*; import java.rmi.server.*; public class RMIServer { static CurrTimeImpl serverObject; public RMIServer() { while (true) { // just keep running the loop to make the object available } } public static void main(String[] args) { System.setSecurityManager(new RMISecurityManager()); try { String remoteObjectName = "ServerTimeObject"; serverObject = new CurrTimeImpl(remoteObjectName); System.out.println(remoteObjectName+" is started"); } catch (Exception e) { System.out.println("Exception occurred while initiating Remote Server Object : " + e.getMessage()); } RMIServer rmiServer = new RMIServer(); } }
The server application is compiled using the javac compiler. One important task performed by the server main program is to instantiate and set the system security manager, which ensures that a proper security policy is in place to access the system resources, including access to the ports, access to read and write disk files, and so on. The security manager should be set before the remote object is bound with the rmiregistry. When the security manager is set, it looks for a proper security policy file, which contains the security definitions for permissions required by the security manager. The demo application has the following entry in the security policy file RMIObjAccess.policy.
grant { permission java.net.SocketPermission "*:1024-65535", "listen,accept,connect"; };
However, the file might include additional permissions if required, as shown below. The following entries provide permission to read all the files in the /RMIExample/Data directory and to read and write all the files in the /RMIExample /Logs directory.
grant { permission java.io.FilePermission "/RMIExample/Data/*", "read"; permission java.io.FilePermission "/RMIExample/Logs*", "read,write"; };
The location of the security permission file is specified as a system property (or as an argument to the Java Virtual Machine) using the –D option while executing the main program. The command-line sample is shown here for a better understanding of how to execute the RMI server application. In this command line, two arguments are passed to the Java Virtual Machine: one is the java.rmi.server. codebase setting, which indicates the directory that contains the server class and stub class files, and the other is the java.security.policy setting, which indicates the security policy file name. It is necessary that the complete path be specified for both these arguments, instead of relative path.
$ export CODEBASE=/root/J2EEProjects/RMIExample $ java -Djava.rmi.server.codebase=file:$CODEBASE/ \ -Djava.security.policy=file:$CODEBASE/RMIObjAccess.policy RMIServer
For convenience, these command lines are saved in an executable shell script file with the name runRMIServer.sh. There is a similar shell script file runRMIClient.sh to run the client application.
Finally, the client application is built as displayed in Listing 7.13 and compiled using the javac compiler. From this listing, it should be noted that the client application attempts to obtain a reference to the remote interface—using the java.rmi.Naming class’s lookup method—before any attempt to use the interface. At this time, a remote exception is raised if the remote object is not available for a variety of reasons, which might include that the server object is not instantiated, the server application is not running, the rmiregistry is not running, or the security policy is not in place. Listing 7.13 is available on the accompanying CD-ROM.
import java.util.*; import java.rmi.*; public class RMIClient { CurrTime remoteServerTime; String remoteObjectServerName; String remoteObjectName = "ServerTimeObject"; public RMIClient() { } public void getRemoteObject() { try { try { System.setSecurityManager(new RMISecurityManager()); remoteObjectServerName = java.net.InetAddress.getLocalHost().getHostName(); CurrTime remoteServerTime = (CurrTime) Naming.lookup("//" + remoteObjectServerName + "/" + remoteObjectName); String serverTimeStr = remoteServerTime.getCurrentTimeString(); System.out.println("Server time obtained as string "+serverTimeStr); Calendar servTime = remoteServerTime.getCurrentTime(); System.out.println("Server time obtained as Calendar object " +servTime.getTime().toString()); System.out.println("Setting server time..."); Calendar localTime = Calendar.getInstance(); remoteServerTime.setCurrentTime(localTime); System.out.println("Server time set successfully..."); servTime = remoteServerTime.getCurrentTime(); System.out.println("Updated Server time obtained as Calendar object " +servTime.getTime().toString()); } catch(Exception e) { System.out.println(e.getMessage()); } } catch(Exception e) { System.out.println(e.getMessage()); } } public static void main(String[] args) { RMIClient RMIClient1 = new RMIClient(); RMIClient1.getRemoteObject(); } }
Follow the steps mentioned here to test the RMI client and server applications.
Run the rmiregistry console program from the $JAVA_HOME/bin directory, where the JAVA_HOME environment variable contains the directory in which J2SDK (Version 1.3) is installed. The programs may work with earlier or later versions of Java 2; however, they are compiled and tested with Version 1.3.
Run the runRMIServer.sh script to start the RMI server application. If the rmiregistry is started successfully, the security policy file is in place as outlined earlier, then the server application starts without any problem; otherwise, a runtime exception will be raised accordingly.
Run the runRMIClient.sh script to start the RMI client application. The client application starts and displays messages as it executes methods on the remote object. Figure 7.3 displays the screenshot of the client application console with appropriate messages.
The source listings presented in this section are not bound with a specific package name, in order to make the demonstration simple. However, like any other Java application, these programs may be packaged in a single package as desired. It should also be noted that the most popular Java IDE—Borland JBuilder—is capable of developing RMI applications with ease and is available in the Linux platform. Most of the Java sample programs in this book are developed and tested in Borland JBuilder8, although some of them are also tested in Oracle 9i JDeveloper (Release 9.0.3).
< Day Day Up > |