Remote Method Invocation


The Sun-supplied Jini services rely heavily on the Remote Method Invocation (RMI) framework. Understanding RMI is critical to understanding Jini.

RMI is another Java-based technology enabling distributed processing. RMI enables Java applications running on different Java Virtual Machines (JVMs) to communicate. Whether these JVMs exist on the same host machine or on different machines does not matter. Just like RPCs (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 RMI system has been specifically designed to operate in the Java application environment. Client applications invoke local calls through a stub interface that communicates with the actual remote object, as seen in Figure 14.1. 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 14.1. Client invocation using RMI.

graphics/14fig01.gif

In the Java platform's distributed object model, a remote object is described by one or more remote interfaces, which are written in the Java programming language. A remote interface is a Java interface that extends java.rmi.Remote and defines the methods, which are 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:

 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 affect this capability. Like P2P systems, firewalls are one of the most frequently encountered inhibitors to RMI communication. To address this issue, RMI uses direct and indirect forwarding. RMI creates HTTP tunnels that encapsulate remote requests, and the Java Remote Method Invocation Protocol (JRMP).

Figure 14.2 demonstrates indirect forwarding, which requires a CGI script running on the remote server to forward requests to the RMI server. Because this is still a hit-and-miss proposition, it has limitations.

Figure 14.2. The HTTP tunnel for RMI.

graphics/14fig02.gif

The RMI Registry

Let's look at how remote objects in RMI are discovered.

The RMI registry is used to associate a remote object with a user-defined name. This name is simply a Java String. The java.rmi.Naming class provides the methods to bind and unbind an object to a name using the RMI registry. The name takes the form of

 [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 and Activatable Services

RMI provides two convenience classes that remote object implementations can extend to facilitate remote object creation, both shown in Figure 14.3:

  • java.rmi.server.UnicastRemoteObject

  • java.rmi.activation.Activatable

Figure 14.3. Class hierarchy for remote objects.

graphics/14fig03.gif

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.

The UnicastRemoteObject class is as follows:

 public class UnicastRemoteObject extends RemoteServer{     protected UnicastRemoteObject()    protected UnicastRemoteObject(int port)    protected UnicastRemoteObject(int port, RMIClientSocketFactory csf,  graphics/ccc.gifRMIServerSocketFactory 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 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 make use of socket factories to change the underlying socket behavior to support additional connection features, such as security and encryption.

Implementing Unicast Services

Let's start with a simple AgentService that implements the Agent interface and extends UnicastRemoteObject:

 public class AgentService extends UnicastRemoteObject          implements Agent {    public AgentService() throws RemoteException {         super();    } 

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 AgentService:

 public static void main(String[] args) {     // must set a security manager to load classes    if(args.length < 1) {         System.out.println("usage [hostname]");         System.exit(1);    }    // set the hostname from the first argument    String hostname = args[0];    if (System.getSecurityManager() == null) {         System.setSecurityManager(new RMISecurityManager());    }    try {         Agent agent = new AgentService();         // prepare to bind the server with a name         String name = "//" + hostname + "/Agent";         Naming.rebind(name, agent);             System.out.println("AgentService bound");         } catch (Exception e) {             System.err.println("AgentService exception: " +             e.getMessage());         }    }    // implementation of the Agent interface    public String talk() {         return "P2P is very cool";    } } 

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 filesystem 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 "", ""; }; 

This is a simple text file that you pass the location to as a property of the Java virtual machine when you invoke the program.

After you have installed a security manager and exported the remote object to RMI, you need to associate a name to the object so clients can find the service. As mentioned before, you use the java.rmi.Naming class to do this. The Naming class takes a name that is a URL-formatted java.lang.String.

A sample fragment would look like the following:

 String name = "//hostname/AgentService";  Naming.rebind(name, agent); 

This code fragment associates the name AgentService 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 to complete our service is to implement the Agent interface:

 public String talk() {     return "P2P is very cool"; } 

The implementation of the interface can be as simple or as complex as required. Standardizing the interface would enable P2P agents from different systems to engage in conversations resembling human communication.

Implementing Activatable Services

A unicast service must be started manually. An activatable service is one that RMI can start automatically. 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.

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.

The java.rmi.activation.Activator interface provides the basic functionality of activation. The Sun implementation of this interface is provided through sun.rmi.server.ActivationGroupImpl, and is invoked by starting the RMI daemon process.

rmid (the RMI daemon) is a daemon process that is started when you boot or reboot your system. The typical command to start the process is

 rmid  J-Dsun.rmi.activation.execPolicy=policy.file  

In addition to the Activator, other key elements of the activation model include the following:

  • java.rmi.activation.ActivationGroup, which is a group of services that have been identified to share a common JVM.

  • 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.

  • java.rmi.activation.ActivationSystem, which provides the interface to register activatable objects and groups.

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 fragment:

 public class AgentService extends Activatable          implements Agent {    public AgentService(ActivationID id, MarshalledObject data)         throws RemoteException    {         // register object using anonymous port         super(id, 0);    } 

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, which invokes a special constructor that takes two arguments: the group's ActivationGroupID, and the group's initialization data (in a java.rmi.MarshalledObject).

You can 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 your 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. The following example creates a group description:

 // 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); 

Now you need to create a specific object description. An object 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 second argument to the remote object's constructor for objects to use during re-initialization/activation:

 ActivationDesc ad =       new ActivationDesc("AgentService", // class name                          codebase,        // codebase                          null,            // no marshalled data passed                          true);           // restart 

Register the object with rmid and then bind the name to the registry:

 Agent agent = (Agent) Activatable.register(ad);  String name = "//" + hostname + "/AgentService"; Naming.rebind(name, agent); 

That is all that is required to create an activatable object. You now have an object implementation that can be created on demand, and if a failure occurs, it will be automatically started when the rmid process is restarted.

RMI's Role in P2P

RMI provides P2P systems with the capability to exchange objects not just messages or XML. These objects are capable of running in the JVM they are downloaded into. This is a powerful concept in distributed computing that has yet to gain widespread adoption. One reason for this is because of the security considerations that must be addressed. Code from untrustworthy sources can do considerable damage if left unchecked. Security must be standardized through security managers like the RMI security manager. This level of homogenous platform architecture across the Internet has not been achieved. Therefore, RMI and Jini can play a role in P2P systems, but most likely that will be limited to Java-based implementations.



JavaT P2P Unleashed
JavaT P2P Unleashed
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 209

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net