Remote Method Invocation
RMI enables Java applications running in different Java Virtual Machines (JVM) to communicate. Whether these JVMs exist on the same host machine or on different machines does not matter. Of course, this is not new to distributed processing. Just like RPC (Remote Procedure Calls), the processes need not exist in the same address space or even on the same machine. However, RMI is able to offer a number of advantages over other distributed models because it assumes the environment is Java-enabled.
The Java platform's remote method invocation system has been specifically designed to operate in the Java application environment. The RMI system assumes the homogeneous environment of the JVM; therefore, the system can take advantage of the Java platform's object model.
Client applications invoke local calls through a stub interface that communicates with the actual remote object as illustrated in Figure 3.3. The RMI runtime performs all the necessary communication housekeeping to ensure that two processes running on separate JVMs can exchange invocation requests and results through an exposed common interface definition.
Figure 3.3 Client invocation using RMI.
In the Java platform's distributed object model, a remote object is described by one or more remote interfaces, which are interfaces written in the Java programming language. A remote interface is a Java interface that extends the java.rmi.Remote interface and defines the methods, which are all made available to remote clients. All methods declared in the interface that can be invoked remotely must declare that they throw a java.rmi.RemoteException. For example, the following simple Agent interface extends Remote, and the talk method throws RemoteException. The object that implements this interface is exposing its talk method to clients in other JVMs.
Listing 3.1 The Agent Interface
import java.rmi.Remote; import java.rmi.RemoteException; public interface Agent extends Remote { public String talk() throws RemoteException; }
A client with a reference to the Agent can invoke the talk method regardless of where the Agent implementation physically resides. Of course, there are network and security considerations that impact this capability. Firewalls are one of the most frequently encountered inhibitors to RMI communication. Security will be discussed in detail in Chapter 9, "Security in Jini."
The RMI Registry
In the previous chapter, you saw the importance of discovery to the emerging networked applications. So how are remote objects in RMI discovered?
The remote object registry (RMI registry) is a repository for associating a name with a remote object. The java.rmi.Naming class provides methods for storing and obtaining references to remote objects in the rmiregistry. The Naming class's methods (bind, unbind, rebind) take as one of their arguments a name that is a URL-formatted java.lang.String of the form:
[rmi:][//][host][:port][/name]
where:
-
rmi names the protocol and may be omitted
-
host is the host (remote or local) where the registry is located,
-
port is the port number on which the registry accepts calls,
-
name is a string that represents the remote object.
Both host and port are optional. If host is omitted, the host defaults to the local host. If port is omitted, then the port defaults to 1099.
Binding a name to a remote object associates the name with the remote object. If you have used other distributed object systems such as CORBA, this process should not be new. A remote object can be associated with a name using the Naming class's bind or rebind methods. For security reasons, the Naming.bind, Naming.rebind, and Naming.unbind methods can only be executed on the same host as the RMI registry.
After a remote object is registered (bound) with the RMI registry on the local host, callers on a remote (or local) host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object.
Unicast Servers
RMI provides convenience classes that remote object implementations can extend to facilitate remote object creation. These classes are java.rmi.server.UnicastRemoteObject and java.rmi.activation.Activatable.
A remote object implementation must be exported to RMI. Exporting a remote object makes that object available to accept incoming calls from clients. For a remote object implementation that is exported as a UnicastRemoteObject, the exporting involves listening on a TCP port for incoming calls. More than one remote object can accept incoming calls on the same port. A remote object implementation can extend the class UnicastRemoteObject to make use of its constructors that export the object, or it can export the object via UnicastRemoteObject's static exportObject methods.
Listing 3.2 The UnicastRemoteObject Class
public class UnicastRemoteObject extends RemoteServer{ protected UnicastRemoteObject() protected UnicastRemoteObject(int port) protected UnicastRemoteObject(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) public Object clone() public static RemoteStub exportObject(Remote obj) public static Remote exportObject(Remote obj, int port) public static Remote exportObject(Remote obj, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) public static boolean unexportObject(Remote obj, boolean force) }
The no argument constructor of the UnicastRemoteObject, creates and exports a remote object on an anonymous port chosen at runtime. The second form of the constructor takes a single argument, port, that specifies the port number on which the remote object accepts incoming calls. The third constructor creates and exports a remote object that accepts incoming calls on the specified port via a ServerSocket created from the RMIServerSocketFactory. Clients make connections to the remote object via Sockets supplied from the RMIClientSocketFactory. You will make use of socket factories to change the underlying socket behavior to support additional connection features, such as security and encryption. Chapter 9 will examine customizing socket connections.
Let's run through an example that demonstrates the power of RMI and, in the process, prepares your environment for Jini.
Building an RMI Non-Activatable Service
You will define, build, and test a command processor that will enable you to invoke commands on a remote machine and return an object that represents the command's result. The steps involved include the following:
-
Define the example command processor.
-
2. Build the example command processor, demonstrating the necessary files required.
-
Test the example command processor and supporting RMI environment.
Define the remote interface (in this example, the Shell interface that extends java.rmi.remote).
Create an RMI service implementing the remote interface.
Create a client application and example command.
Compile the files.
Create the required stub interfaces.
Create jar files (client-side and server-side).
Define the security policy files for client and server.
Start the RMI registry.
Start the RMIShell server.
List the registry contents to verify setup.
Test the client.
Define Remote Interface (extend java.rmi.remote)
Interfaces are important to Jini and RMI. You'll build a command processor that will enable us to invoke commands on any machine on the network using the Shell interface.
You'll use the familiar Command pattern (see Design Patterns by Erich Gamma, et al. for more information) to define an interface that has an execute method and returns an object. This enables us to define a simple but powerful interface. Notice we are not extending the java.rmi.Remote interface, but rather Serializable.
Listing 3.3 The Command Interface
public interface Command extends java.io.Serializable { public Object execute(); }
The Command interface will be used as a parameter of the remote interface Shell as seen in the following code. Therefore, the Command interface is not the interface that you expose and invoke remotely. Instead, it will be used as a parameter to the remote interface. Parameters that are defined in the method signatures of an interface must be serializable so that they can be serialized (converted into byte streams) and transported between virtual machines. Additionally, parameters that implement remote interfaces are passed as stubs (references) to the calling process.
The remote interface Shell takes a Command object as an argument, extends java.rmi.remote, and throws RemoteException.
Listing 3.4 The Shell Interface
public interface Shell extends java.rmi.Remote { public Object executeCommand(Command cmd) throws RemoteException; }
The two interfaces, Shell and Command, are delivered to developers of clients and servers of the command processor. This is all that would be required to enable developers anywhere in the world to start developing commands that would work in the processor framework.
Of course, the more developers building and sharing Command objects, the better. One hundred developers building command processors based on this interface would make things both interesting and exciting. One thousand developers building and linking command processors around the globewell, we just don't know!
Create RMI Service That Implements Remote Interface
Let's start with a simple command processor, RMIShell. RMIShell implements the Shell interface and extends UnicastRemoteObject.
Listing 3.5 The RMIShell Class
// Our Command Processor Class public class RMIShell extends UnicastRemoteObject implements Shell { public RMIShell() throws RemoteException { super(); }
As mentioned previously, the UnicastRemoteObject class defines a remote object whose references are valid only while the server process is running. The UnicastRemoteObject class provides support for point-to-point active object references using TCP streams.
Because you are extending UnicastRemoteObject, the object is automatically exported to the RMI runtime during construction. Inheriting from UnicastRemoteObject eliminates the need to call exportObject.
Let's look at the main method for RMIShell.
Listing 3.6 The RMIShell Class Continued
// Our service requires remote behavior and extends UnicastRemoteObject import java.rmi.server.UnicastRemoteObject; // Our interface definitions import org.jworkplace.command.Command; import org.jworkplace.command.Shell; public static void main(String[] args) { if(args.length < 1) { System.out.println("usage [hostname]"); System.exit(1); } // set the hostname from the 1st argument String hostname = args[0]; // must set a security manager to load classes if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { Shell shell = new RMIShell(); // prepare to bind the server with a name String name = "//" + hostname + "/RMIShell"; Naming.rebind(name, shell); System.out.println("RMIShell bound"); } catch (Exception e) { System.err.println("RMIShell exception: " + e.getMessage()); e.printStackTrace(); } } // implementation of the Shell interface public Object executeCommand(Command cmd) { return cmd.execute(); } }
The first thing you must do is install and set an appropriate security manager that protects access to system resources from untrustworthy downloaded code. Forgetting to do this will result in a runtime error, which will throw a java.lang.SecurityException. The security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.
All programs using RMI must install a security manager, or RMI will not download classes other than from the local classpath in remote method calls. This restriction ensures that the operations performed by downloaded code go through a set of security checks. In addition to installing a security manager, you also define an associated security policy.
grant { permission java.security.AllPermission "", ""; };
Security will be examined in detail in Chapter 9. For now, just ensure that you have a policy file available that grants all permissions, similar to the one shown previously. You can save it in a working directory as policy.all. This should only be used in a test situation where access to outside networks is nonexistent or well-controlled. You will eventually set the policy file controlling the process at runtime, by setting a property on the command line, when you start the service.
After you have installed a security manager and exported the remote object to RMI, you need to associate a name to the object so that clients can find the service. As mentioned before, this is done by using the java.rmi.Naming class. The Naming class takes a name that is a URL-formatted java.lang.String of the form:
[rmi:][//][host][:port][/name]
A sample fragment would look like the following:
String name = "//hostname/RMIShell"; Naming.rebind(name, shell);
This code fragment associates the name RMIShell with the remote object running on the designated host machine. The port is optional and defaults to 1099, which is the default port for the RMI registry.
The only thing left necessary to complete our service is to implement the Shell interface.
public Object executeCommand(Command cmd) { return cmd.execute(); }
The implementation invokes the Command interface's execute method and returns the resulting object to the client. Of course, the server has no idea what the command actually is, because it is passed to the server as a parameter in the method invocation. In this case, the client is providing the server with the command to execute. Because we have not set any security, almost any command could be passed, which is a dangerous proposition.
Let's look at how the client code is developed.
Create a Client Application and Example Command
The client implements the CommandLine class and also defines a class that implements the Command interface.
Listing 3.7 The CommandLine Class
import java.rmi.*; import org.jworkplace.command.*; public class CommandLine { public static void main(String args[]) { if(args.length < 2) { System.out.println(" usage [hostname] [remote directory]"); System.exit(1); } // even clients must set a security manager if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { // we pass the hostname in as an argument and perform lookup String name = "//" + args[0] + "/RMIShell"; Shell shell = (Shell) Naming.lookup(name); // we define a class that implements the Command interface // argument 1 represents a directory on a remote machine FileList command = new FileList(args[1]); // execute command and cast result object String[] files = (String[]) (shell.executeCommand(command)); // error invalid directory supplied if(files == null) { System.out.println("Invalid directory"); } else { // display results to console for(int i=0; i<files.length; i++) System.out.println(files[i]); } } catch (Exception e) { System.err.println("FileList exception: " + e.getMessage()); e.printStackTrace(); } } }
The CommandLine class first installs a security manager. Like the server-side development, you will also need a very liberal client-side security policy file:
grant { permission java.security.AllPermission "", ""; };
You will use the java.rmi.Naming.lookup() method to get a reference to a remote object. This is the same name that was supplied to the server when it was registered: //hostname/RMIShell.
The RMI runtime will return an instance of the Shell remote interface. This is actually a reference to a remote interface that the local stub object will use to resolve to the remote implementation object when the remote object is accessed by calling the executeCommand method.
The command object developed in this example is a FileList object.
Listing 3.8 The FileList Class
import java.io.File; import org.jworkplace.command.*; // implements our serializable Command interface public class FileList implements Command { private String path; // we paramaterize the command with a path public FileList(String path) { this.path = path; } // invoke the command and return object public Object execute() { File file = new File(path); // simple file list return file.list(); } }
The FileList class implements the Command interface. The constructor takes a directory path as a parameter. The execute method creates a File object and returns a String[] array of file and directory names.
On the command line, you supply the remote directory name, which in this case is args[0]. This enables us to get a directory listing from a remote machine. The remote machine executes the command locally and returns the result to the client. The remote machine does not know the command that is being executed. In fact, the remote machine does not even have the FileList class available.
This might be a good point to stop and look at the sequence of exchanges that are supporting the command processor, as illustrated in Figure 3.4.
-
The RMIShell registers with the RMI registry using the RMI protocol.
-
The RMI registry uses the HTTP server identified by the codebase property of the startup script to download the required files (for instance, RMIShell_Stub.class in rmishell-dl.jar) using the URL protocol.
-
The client retrieves the remote reference to the RMIShell interface by invoking the java.rmi.Naming class to resolve the name-to-object reference.
-
The client downloads the required files from the server-side HTTP server to invoke the service.
-
The client invokes executeCommand, which is processed locally by the stub class RMIShell_Stub. RMIShell_Stub and the RMI runtime marshals (formats) the parameters necessary to invoke the command remotely and sends the request using the RMI protocol.
-
The RMIShell implementation (actually the RMI runtime classloader) requires the definition of the FileList class from the client and accesses the class definition from the client-side HTTP server using the URL protocol.
Figure 3.4 Sequence of exchanges in the Filelist command example.
As you can see, there is a lot of communication going on under the covers in this scenario. It requires an HTTP server on the client-side and the server-side to satisfy requests for missing class definitions. This capability to dynamically move code to the point of execution demonstrates a significant difference between RMI and other distributed object systems.
The client contacted the HTTP server on the implementing host and required the remote stub interface (RMIShell) to download the required classes. The server required the definition of the command FileList and contacted the client-side HTTP server to download the required classes.
You could have also defined a scenario where a parameter passed would have been implemented as a Remote interface. In this case, a stub (reference) would have been downloaded from the client and a remote request from the server to the client would have resulted. In effect, a callback to the client would have made the server a client of the client.
Compile the Files
Change to the chapter3 directory, where you copied the examples for this book; for example, C:>cd \chapter3\example1. You should see the following Java files along with other files that will help you build the service:
Command.java
CommandLine.java
FileList.java
RMIShell.java
Shell.java
There is a compile.bat file included that will compile the files. Simply invoke:
C:\chapter3\example1>compile
This should create the necessary class files in the current directory and the stub file RMIShell_Stub.class.
Create the Required Stub Interfaces
The RMI stub protocol is used by the stub to communicate with the server. To generate a stub, you must invoke the rmic compiler for the remote service you are defining; in this case RMIShell. The supplied compile.bat file invokes the rmic to generate the stub file for the RMIShell class. You'll be using the 1.2 version of rmic because we are assuming JDK 1.2 or later support across the environment.
Create Jar Files (Client-Side and Server-Side)
There is a jars.bat file included in the directory that generates the necessary jar files for deployment. Simply invoke
C:\chapter3\example1>jars
You should study the commands in the jars file, so that you understand how the jar files are created, and perhaps more importantly, why.
The jar files generate the following:
-
service.jarContains the RMIShell command processor and supporting classes.
-
service-dl.jarContains the RMIShell_Stub class and supporting classes.
-
client.jarContains the CommandLine client and supporting classes.
-
client-dl.jarContains the Command and the FileList classes.
The jars.bat also uses manifest files to identify the main class in each jar file (clientservice) to be invoked at runtime. As a general rule, you will use the -jar option when invoking java. This helps to alleviate classpath problems by ensuring that all files are accessed from jar files as opposed to current classpath settings. Getting into this habit of construction will help in your future development.
Copy the command-dl.jar file to the client HTTP directory. This is the same location that you used to start the HTTP server.
Copy the client-dl.jar and service-dl.jar file to the appropriate HTTP directories: the client-dl.jar to the client HTTP server directory and the service-dl to the service HTTP directory, as seen in Figure 3.5.
Figure 3.5 Client and server HTTP directory display.
Define the Security Policy Files for Client and Server
Create the policy.all file for both the server and the client:
grant { permission java.security.AllPermission "", ""; };
These files will set the security permissions to use for the JVM when you run the Java programs.
Start the RMI Registry
On the server, unset the classpath and start the RMI Registry:
C:\>setenv CLASSPATH= C:\> rmiregistry&
Try to use the same directory every time you start the RMI Registry until you are sure that your environment is set up properly. Also, avoid starting the registry in the same directory in which you are about to run your server. The classpath for the downloaded jar files will not be set properly if the required files are found in the current directory. If you use the convention (as shown) of always creating jar files, this should eliminate some of the potential problems.
Start the RMIShell Server
Go to the directory where the RMIShell has been developed. Start the RMIShell command processor. In this example there is a "service" directory under example1 that contains a startService script.
The script defined for Windows is startService.bat. It is invoked by the command:
C:\JiniJavaSpaces\chapter3\example1\service>startService remote_host_name
The remote_host_name argument is the hostname or IP address where the RMI Registry is running. For instance, you would invoke startService 172.16.1.2 if you were running RMI Registry on IP address 172.16.1.2.
The startService script requires that an environment variable, HTTP_ADDRESS, be set prior to running the script. The HTTP_ADDRESS is the location of the running HTTP_SERVER. For example:
set HTTP_SERVER=172.16.1.2:8081
would indicate that the HTTP server is running on the host address 172.16.1.2 at port 8081.
The startService is defined as follows:
Listing 3.9 The startService.cmd script
@echo off if not "%HTTP_ADDRESS%" == "" goto gotServerHTTP echo You must set HTTP_ADDRESS to point at your service HTTP server echo For example set HTTP_ADDRESS=[hostname][:port] goto exit :gotServerHTTP java -jar -Djava.security.policy=%JINI_HOME%\policy\policy.all - Djava.rmi.server.codebase=http://%HTTP_ADDRESS%/service-dl.jar service.jar %1 :exit
You will notice the script requires that two additional parameters be set -Djava.security.policy and the -Djava.server.codebase.
The -Djava.security.policy parameter is set to the server-side location of the policy.all file. The script defaults to the Jini distribution file at \policy\policy.all.
The -Djava.rmi.server.codebase parameter is set to the server-side HTTP server address (hostname:port) and the jar file (service-dl.jar) you make available to clients.
You should see on the HTTP console the access to the service-dl.jar file. If you do not see this access, then you might have a classpath problem. The registry should gain access to the class definitions using the HTTP server, and not on the local classpath. If the registry does not use the HTTP server, you need to verify that the classpath was not set when you started the RMI registry. Stop the registry and verify.
List the Registry Contents to Verify Setup
There is a list registry program that you can use on the server to verify that the RMIShell service is registered and active as RMIShell in the registry. The program is ListRMIServices and is in the directory \utilities.
Copy the program to a directory on a machine that has access to the RMI registry. To verify, run the program using the following command:
C:\JiniJavaSpaces\utilities\java ListRMIServices
You should see the service listed on the console as:
rmi://:1099/RMIShell
Test Client
To test the client, move to the directory and machine where you deployed the client software. Run the following client command:
C:\JiniJavaSpaces\chapter3\example1\client>startClient remote_host_name remote_host_directory
Where -Djava.security.policy is the path to your policy file, and -Djava.rmi.server.codebase is set to the client-side HTTP server address (hostname:port) and the jar file you will make available to servers. remote_host_name is the name of the remote host, and remote__host_directory is the remote directory to search and display files.
Again the script requires that the environment variable for HTTP_ADDRESS be set to the location of the client-side HTTP server. For example, on the command line type:
set HTTP_SERVER=[hostname][:port]
where hostname is the name or IP address of the machine running the client-side HTTP server; and :port is the port number you assigned or the default 8080.
If you are trying this example on a single machine, you will have to change either the server or client port assignment; otherwise, a port conflict will result.
You will see the contents of the remote directory displayed on the console.
You have just overcome the first major hurdle in establishing an RMI-Jini environment. Congratulations!
Building on the Java 2 Platform
With Java 2, RMI defined two types of service based on activation policyactivatable and non-activatable. A non-activatable service is one that you must manually startlike the RMIShell service you just defined.
RMI also supports activatable services. An activatable service is one that RMI can start automatically, depending on its configuration. RMI has the capability to restart services when it is activated or to start services when they receive their first incoming call. This automatic activation is instrumental to enabling a more robust distributed environment. An activation daemon takes care of restarting, deactivating, and reactivating services. As I'll demonstrate, Jini uses this environment in a number of interesting ways.
The Intelligent Socket
The next approach to writing RMI services is by extending Activatable as opposed to UnicastRemoteObject as seen in Figure 3.6. There are a number of key components to the activation model that should be highlighted. Because many of the Jini services are activatable services, this background will be beneficial.
Figure 3.6 Class hierarchy for remote objects.
The java.rmi.activation.Activator interface provides the basic functionality of activation. The Sun implementation of this interface is provided through the sun.rmi.server.Activation._GroupImpl and is invoked by starting the RMI daemon process.
The rmid is a daemon process that is started when you reboot your system. The typical command to start the process is:
rmid -J-Dsun.rmi.activation.execPolicy=none
Of course, this is bypassing security constraints and continues with the theme in this chapter of gratuitous security policiesa theme that will change in subsequent chapters!
In addition to the Activator, other key elements of the activation model include:
-
The java.rmi.activation.ActivationGroup, which is a group of services that have been identified to share a common JVM.
-
The java.rmi.activation.ActivationMonitor, which tracks and monitors the state of an object in an activation group and the state of the activation group as a whole.
-
The java.rmi.activation.ActivationSystem, which provides the interface to register activatable objects and groups.
Building an RMI-Activatable Service
Let's outline the changes necessary to make the RMIShell an activatable service.
-
Change inheritance from UnicastRemoteObject to Activatable.
-
Create an activation group.
-
Create an activation object description.
-
Register the activation description with rmid.
-
Bind the stub to the registry.
The RMIActivatableShell class provides an example of an RMI activatable service.
Listing 3.10 The RMIActivatableShell Class
package org.jworkplace.service; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.util.Properties; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationDesc; import java.rmi.activation.ActivationID; import java.rmi.activation.ActivationGroup; import java.rmi.activation.ActivationGroupDesc; import java.rmi.activation.ActivationGroupID; import java.rmi.MarshalledObject; import org.jworkplace.command.*; // extend Activatable public class RMIActivatableShell extends Activatable implements Shell { public RMIActivatableShell(ActivationID id, MarshalledObject data) throws RemoteException { // register object with activation system using anonymous port super(id, 0); } // our implementation public Object executeCommand(Command cmd) { return cmd.execute(); } public static void main(String[] args) { if(args.length < 3) { System.out.println("usage [hostname] [codebase] [policy]"); System.exit(1); } String hostname = args[0]; String codebase = args[1]; String policy = args[2]; // set security manager try { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } // create the activation group description Properties props = new Properties(); props.put("java.security.policy", policy); // set class path props.put("java.class.path", System.getProperty("java.class.path")); props.put("java.rmi.server.codebase", codebase); ActivationGroupDesc agd = new ActivationGroupDesc(props, null); ActivationGroupID agi = ActivationGroup.getSystem().registerGroup(agd); ActivationGroup.createGroup(agi, agd, 0); // create the activation object description ActivationDesc ad = new ActivationDesc("org.jworkplace.service. RMIActivatableShell", codebase, null, true); // register the activation description with rmid Shell shell = (Shell) Activatable.register(ad); // bind the stub to the registry String name = "//" + hostname + "/RMIActivatableShell"; Naming.rebind(name, shell); System.out.println("RMIActivatableShell bound"); System.exit(0); } catch (Exception e) { System.err.println("RMIActivatableShell exception: " + e.getMessage()); e.printStackTrace(); } } }
Change Inheritance From UnicastRemoteObject to Activatable
The first change is to redesign the class hierarchy so that the new shell inherits from Activatable. The Activatable constructor will export the object just like UnicastRemoteObject did.
The activation system will call the constructor with a unique activation ID and a marshalled object that contains any information that you want to pass to the activated object. This information is passed when you register the service, as illustrated in the following:
public class RMIActivatableShell extends Activatable implements Shell { public RMIActivatableShell(ActivationID id, MarshalledObject data) throws RemoteException { // register object with activation system using anonymous port super(id, 0); }
Create an Activation Group Description
An activation group descriptor contains the information necessary to create and re-create an activation group in which to activate objects. The description contains:
-
The group's class name
-
The group's code location (the location of the group's class)
-
A "marshalled" object that can contain group-specific initialization data
The group's class must be a concrete subclass of ActivationGroup. A subclass of ActivationGroup is created/re-created via the ActivationGroup.createGroup static method that invokes a special constructor that takes two argumentsthe group's ActivationGroupID, and the group's initialization data (in a java.rmi.MarshalledObject).
You construct an activation group descriptor that uses the system defaults for group implementation and code location. Properties specify Java environment overrides (which will override system properties in the group implementation's JVM). The command environment can control the exact command/options used in starting the child JVM, or can be null to accept rmid's default.
// set the policy and codebase properties to start the group Properties props = new Properties(); props.put("java.security.policy", policy); props.put("java.rmi.server.codebase", codebase); // use the rmid default command environment ActivationGroupDesc agd = new ActivationGroupDesc(props, null); // get the activation group id ActivationGroupID agi = ActivationGroup.getSystem().registerGroup(agd); // create the group ActivationGroup.createGroup(agi, agd, 0);
Create an Object Activation Description
You've just created a group description, now you need to create a specific object description. An activation descriptor contains the information necessary to activate an object, which includes the following:
-
The object's group identifier
-
The object's fully qualified class name
-
The object's code location (the location of the class) and a codebase URL path
-
The object's restart mode
-
A marshalled object that can contain object-specific initialization data
A descriptor registered with the activation system can be used to re-create/activate the object specified by the descriptor. The MarshalledObject in the object's descriptor is passed as the third argument to the remote object's constructor for objects to use during reinitialization/activation.
ActivationDesc ad = new ActivationDesc("RMIActivatableShell", // class name codebase, // codebase null, // no marshalled data passed true); // restart
Register and Bind
Register the object with rmid and then bind the name to the registry.
Shell shell = (Shell) Activatable.register(ad); String name = "//" + hostname + "/RMIActivatableShell"; Naming.rebind(name, shell);
That is all that is required to create an activatable object. As a result of the changes, you now have an object implementation that can be created on demand, and if a failure occurs, will be automatically started when the rmid process is restarted.
Let's return to the GUI-supplied service activation program so you can start rmid and test your activatable service. To start rmid, simply select the RMID tab and set the following properties, as shown in Figure 3.7:
-
RMID command: rmid
-
Options: -J-Dsun.rmi.activation.execPolicy=none
Then, select the Run tab and start rmid. The activation daemon, when started, silently executes unless a parameter for debug information has been set, or something goes wrong! If the service fails to start, you might want to check the "Gotchas and Common Failures" section in Appendix A to troubleshoot the problem.
Figure 3.7 Parameters to start the rmid application.
When rmid starts, it creates a log directory and file used to record the state of the activation system that rmid manages.
Now let's build and test our RMI-activatable Service. The compilation and stub generation is similar to the non-activatable shell you just created.
Compile the Files
Change to the chapter3 directory where you originally copied the examples for this book; for example, C:>cd \chapter3\example2. You will see the following files:
CommandLine.java
FileList.java
Command.java
Shell.java
RMIActivatableShell.java
Invoke the java compiler:
D:\chapter3\example2>compile.bat
Create the Required Stub Interfaces
The RMI stub protocol is used by the stub to communicate with the server. To generate a stub you must invoke the rmic compiler for the remote service you are defining; in this case RMIActivatableShell. The supplied compile.bat file invokes the rmic compiler to generate the stub file for the RMIActivatableShell class.
Create Jar Files (Client-Side and Server-Side)
There is a jars.bat file included in the directory that will generate the necessary jar files for deployment. Simply invoke:
C:\chapter3\example2>jars
The same procedure should be followed as in example1 for coyping the jar files to the appropriate directories. You will replace the current jar files. See example1 earlier in this chapter for details.
Define the Security Policy Files for Client and Server
The policy file setup is the same as in example1.
Start the RMI Registry
On the server, unset the classpath and start the RMI registry.
\usr\files\JWorkPlace>setenv CLASSPATH= \usr\files\JWorkPlace> rmiregistry&
Again, try to use the same directory every time you start the RMI registry until you are sure that your environment is set up properly. Also, avoid starting the registry in the same directory in which you are about to run your server.
Start the RMIActivatableShell Server
Go to the directory where the RMIActivatableShell was developed. Start the RMIActivatableShell command processor. Again, in this example there is a "service" directory under example2 that contains a startService script.
D:\chapter3\example2\service>startService
You should verify the contents of the script. Specifically, the three parameters at the end of the command are for:
-
The hostname of the RMI registry
-
The codebase parameter for service startup
-
The policy file for service activation
This time when you start the service, it will register with the activation system (rmid) and then exit. When the client invokes the FileList command, the activation system will start the service.
Again, you will see the access to the service-dl.jar file on the HTTP console. If you do not see this access, then a classpath problem may be the culprit.
List the Registry Contents to Verify Setup
To verify the service, move to the directory and machine where you deployed the server software:
\usr\files\JWorkPlace>java -Djava.security.policy=/usr/JWorkPlace/policy.all org.jworkplace.rmi.ListRMIServices
You will see the service listed on the console as rmi://:1099/RMIActivatableShell.
Test Client
The test client step is exactly as in example1.
Again, you will see the contents of the remote directory displayed on the console.
You no longer have to start the shell manually. It will be started automatically when the first request to access the service is invoked (see Figure 3.8).
Figure 3.8 Activatable service activation.
Congratulations! You now have an environment in place that provides the necessary support for bootstrapping Jini.
Jini and the RMI Activation Daemon
An RMI activation daemon is needed by several Jini servicesfor example, the Jini LUS, the Transaction Manager, and the persistent JavaSpaces Service. An instance of the daemon needs to be active on each of the machines where Jini services will run. You will see that the RMI framework significantly impacts and influences the design of Jini services.
When a Jini service starts, it registers with rmid. This registration process is run in its own JVM spawned by the RMI daemon. At the completion of registration, the process exits. The registration process consists of passing parameters to rmid that are sufficient to register and start a Jini service or, as was just demonstrated, any remote service. So, there are two steps:
-
The service will register with rmid in a separate JVM.
-
Activation of the service by rmid will also occur in a separate JVM when requested.