Chapter 9: Jini Extensible Remote Invocation

9.1. Jeri

Jini is middleware for distributed processing. As such, it relies on a number of mechanisms for distributed processing. One of these is remote method invocation (RMI). In RMI, an object stays on a server and a dumb proxy is sent to a client. The client makes method calls on this proxy. The proxy just transmits calls across the network to the server, which calls the original object. The result of this method call is then sent back to the proxy which returns the result to the client. This is an object-oriented version of remote procedure calls. It is somewhat similar to CORBA remore references, except that the proxy is a Java object which runs in the client and does not require the "backplane" support of CORBA.

The original implementation of RMI used JRMP (Java Remote Method Procotol). This is a particular protocol built directly on TCP. Since then a number of other ways of doing RMI have emerged, such as RMI over HTTP (the Web transport protocol), RMI over IIOP (the CORBA transport protocol) and RMI over SSL (secure socket layer). There is even an implementation of RMI on Firewire (IEEE 1394), the high-speed transport layer designed for audio-visual data such as high-definition TV.

The different ways of doing RMI each have their own programmatic interface, with specialised classes. This should be abstracted: one mechanism, with configurations used to select the actual protocol implementation used. For example, a configuration file could specify whether to use RMI over TCP or RMI over IIOP. Then the application could be written in an implementation independant way, with protocol chosen at runtime based on runtime configuration information. This is what Jini has now done, and has developed a new protocol Jini ERI which solves some other issues.

9.2. Traditional RMI

Most books on Java include a section on RMI, and there are complete books just about this topic. A class typically subclasses UnicastRemoteObject. After compilation, the RMI compiler rmic is run on the class file to produce a proxy object. When the service is run, this proxy object must be made network visible in some way so that external clients can locate it. This visibility may be to an RMI Naming service, to a Jini registry or to other directory services. Then a client can use the directory to find the proxy object and use this to make remote calls on the original service object.

There is a little bit of chicanery before an object is made network visible: a class registers itself with the Java runtime by an operation called exporting, and then methods that should use the proxy object instead use the original service object. The Java runtime looks out for these and substitutes the proxy object in its place. This means that the programmer does not deal explicitly with the proxy at all,and seemingly writes code that does not use proxies. The Java runtime takes care of substituting the proxy when necessary. However, this may be a little confusing, and certainly does not make it clear exactly what is going on: this kind of trick is not one that will be in most programmer's experience.

The most common way to use RMI is simply to declare an object that extends UnicastRemoteObject and implements the Remote interface


package jeri;

import java.rmi.*;
import java.rmi.server.*;

public class RmiImplicitExportDemo extends UnicastRemoteObject implements Remote { 

    public static void main(String[] args) throws Exception { 
	// this exports the RMI stub to the Java runtime
	// a thread is started to keep the stub alive
	new RmiImplicitExportDemo(); 

	System.out.println("Proxy is now exported");

	// this application will then stay alive till killed by the user
    } 

    // An empty constructor is needed for the runtime to construct
    // the proxy stub
    public RmiImplicitExportDemo() throws java.rmi.RemoteException {
    }
}

This does lots of things in the constructor behind the scenes: uses the class name to construct a proxy object using the zero args constructor, starts an extra thread to keep things alive and registers the object as a remote object requiring special attention. While the intention is to keep things simple, these activities can prove to be a little unsettling.

An alternative approach makes some of these more explicit


package jeri;

import java.rmi.*;
import java.rmi.server.*;

public class RmiExplicitExportDemo implements Remote { 

    public static void main(String[] args) throws Exception { 
	Remote demo = new RmiExplicitExportDemo(); 

	// this exports the RMI stub to the Java runtime
	RemoteStub stub = UnicastRemoteObject.exportObject(demo);

	System.out.println("Proxy is " + stub.toString());
	
	// This application will stay alive until killed by the user,
	// or it does a System.exit()
	// or it unexports the proxy

	// Note that the demo is "apparently" unexported, not the proxy 
	UnicastRemoteObject.unexportObject(demo, true);
    } 
}

Traditionally, this mechanism has only been used when the class has to inherit from some other class and cannot also inherit from UnicastRemoteObject

9.3. Jeri Export

Jeri makes exporting an explicit operation, using a static method of an Exporter class. However, because it can be exporting objects that use a variety of protocols (JRMP, IIOP, etc), it uses a number of levels of indirection to gain a single compile-time object customised in different ways at runtime. Firstly, it gains its Exporter from a Configuration object, which can contain a variety of runtime configuration information. The Configuration object is obtained from a ConfigurationProvider. The ConfigurationProvider can be set to return a custom Configuration, but defaults to a ConfigurationFile which takes a configuration file as parameter.

The ConfigurationFile object has a constructor ConfigurationFile(String[] args). The first element of this list is a filename for a configuration file. The list is passed to the ConfigurationFile constructor from the ConfigurationProvider.getInstance(args) method. Once a configuration object is found, configurable objects can be extracted from it by the Configuration.getEntry(String component, String name, Class type) method. The parameters to this are

  1. component - the component being configured
  2. name - the name of the entry for the component
  3. type - the type of the object to be returned

To make this more concrete, the contents of the configuration file for a ConfigurationFile may be


import net.jini.jrmp.*; 
JeriExportDemo {
    exporter = new JrmpExporter(); 
}

This specifies an Exporter object constructed from a component name JeriExportDemo with name exporter, of type JrmpExporter (which is a subclass of Exporter).

The configuration used can be changed by modifying the contents of the file or by using different files such as jeri/iiop.config:


import net.jini.iiop.*; 
JeriExportDemo {
    exporter = new IiopExporter(); 
}

or by using a file with multiple entries such as jeri/many.config and using different component names to select which one is used:

import net.jini.jrmp.*; 
import net.jini.iiop.*; 

JrmpExportDemo {
    exporter = new JrmpExporter(); 
}

IiopExportDemo {
     exporter = new IiopExporter(); 
}

Once an Exporter has been found, an object can be exported to the Java runtime by the Exporter.export() method. This takes the implementation object and returns a proxy that can be registered with a lookup service or any other remote directory.

The program to export an object using this configuration mechanism is


package jeri;

import java.rmi.*; 
import net.jini.config.*; 
import net.jini.export.*; 

public class JeriExportDemo implements Remote { 

    private static String CONFIG_FILE = "jeri/jeri.config";

    public static void main(String[] args) throws Exception { 
	String[] configArgs = new String[] {CONFIG_FILE};

	// get the configuration (by default a FileConfiguration) 
	Configuration config = ConfigurationProvider.getInstance(configArgs); 
	System.out.println("Configuration: " + config.toString());
	// and use this to construct an exporter
	Exporter exporter = (Exporter) config.getEntry( "JeriExportDemo", 
							"exporter", 
							Exporter.class); 

	// export an object of this class
	Remote proxy = exporter.export(new JeriExportDemo()); 
	System.out.println("Proxy is " + proxy.toString()); 

	// now unexport it once finished
	exporter.unexport(true); 
    } 
}

The exported object, the proxy, is declared to be of the Remote interface. In fact, the specification says that the proxy will implement all of the remote interfaces of the original Remote object. Here there are none, but in general a remote object will implement one or more Remote interfaces, and so will the proxy.

This last point is worth expanding on. Suppose we have an interface Iface which does not extend Remote. The interface RemoteIface extends both Iface and Remote. From there we could have implementations RemoteIfaceImpl and IfaceImpl, and we could generate proxies from each of these using Exporter. The class names of the proxies are automatically generated (and are obscure) so we shall call these classes RemoteIfaceImplProxy and IfaceImplProxy respectively. The resulting class diagram is (with the non-standard dotted arrow showing the generation of the proxies)

Figure 9.1: Proxies Generated by Exporter
Since IfaceImpl does not implement any remote interfaces, then neither does IfaceImplProxy (strictly, it may implement some additional ones, bit probably not ones we are interested in here). There are no inheritance lines leading to IfaceImplProxy But since RemoteIfaceImpl does implement a remote interface, then so does its proxy.

The most notable consequence of this is that the IfaceImplProxy cannot be cast to an Iface whereas IfaceRemoteImplProxy can be:


Iface iface = (Iface) ifaceImplProxy        // class cast error
Iface iface = (Iface) ifaceRemoteImplProxy  // okay
Thus it will be important to include a remote interface somewhere in the interface hierarchy for any implementation.

9.4. JRMP Proxies

The JrmpExporter acts as a layer over the existing RMI runtime system, and just funnels calls through to it. It acts as an adapter between existing methods for exporting and unexporting methods over JRMP.

A class implements the Remote interface. This will act as the implementation object of some service. In order to create a proxy, the rmic compiler will need to be run on the implementation class file. For example,


javac JeriExportDemo.java
rmic -v1.2 -d . JeriExportDemo
This will create a JeriExportDemo_Stub.class proxy. When the Exporter.export() method is called with the implementation object the method will return an instance of this proxy class. This proxy instance can then be registered with lookup services and sent to remote clients.

9.5. Basic Jeri Exporter

Jeri supplies an exporter that creates objects that use the Jini Extensible Remote Invocation protocol (Jini ERI, or JERI). This addresses a number of issues not handled by other protocols

  1. Dynamic generation of proxies (no need to run rmic)
  2. Improved security
  3. Customisation mechanisms
  4. Use of a variety of communication transports, not just TCP
  5. Optional use of distributed garbage collection

Using Jini ERI instead of JRMP is no more complicated than changing the configuration file:


import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

JeriExportDemo {
    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

In fact, it is simpler to use Jini ERI since there is no need to create the proxy file at compile/build time - it can be left to the runtime execution to build the proxy.


If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone . The current edition of the book does not yet deal with Jini 2.0, but the next edition will.


This file is Copyright (©) 1999, 2000, 2001, 2003 by Jan Newmarch (http://jan.netcomp.edu.au) jan.newmarch@jan.newmarch.name.

This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v0.4 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.