Java Remote Method Invocation

RMI

RMI is a Java-specific remote method mechanism. It is simpler than CORBA since it doesn't have to worry about mappings from an IDL into specific languages. Everything is in Java, including the specifications of objects. On the other hand, you can't use this to talk to C++ objects, for example.

RMI over IIOP is an advanced way of using RMI over the CORBA backplane. This will allow RMI calls to CORBA objects, but is beyond this lecture - and is still only in JDK 1.3 which is in beta at May, 2000.

Remote Objects

There is an interface java.rmi.Remote. Interfaces for remote objects should extend this interface

package hello;

public interface Hello extends java.rmi.Remote {
    String sayHello() throws java.rmi.RemoteException;
}
Every method in a remote interface must throw a RemoteException. This is contrary to idea of remote procedure calls that "remoteness is invisible".

Remoteness is visible

RPC tries to hide the network. Sun researchers decided this was impossible

CORBA tries to hide the network by using "unchecked" exceptions for network errors. These have to be caught anyway. Java makes it explicit by making every network call (or possible network call) throw a RemoteException.

Interfaces

RMI services are defined by interfaces. There will later be an implementation that sits in a server, and which delivers a proxy or stub to clients. Both the implementation and the stub implement the interface

Implementation

An implementation of a remote object will implement the interface, and will usually extend java.rmi.UnicastRemoteObject. Java compilers and runtime systems treat UnicastRemoteObject differently to other objects. In some method calls (such as to a naming service) the stub is substituted for the object. The programmer writes code using the object, the runtime replaces it with the stub

package hello;

public class HelloImpl extends java.rmi.server.UnicastRemoteObject
             implements Hello {

    public HelloImpl() throws java.rmi.RemoteException {
        // empty, but needed by RMI
    }

    public String sayHello() {
        return("Hello world");
    }
}

Name service

There is a name service rmiregistry which runs on port 1099. A service will register with this name server by

    void Naming.bind(String url, Object obj);
The url contains the address of the name service and the name of the object within it. For security reasons, the service and the registry must be on the same machine. This is to avoid remote services trashing the registry on your machine.

A client can then retrieve the object by the reverse method of

    Remote Naming.lookup(String url);
It can obtain a list of all the names on a name service by
    String[] Naming.list(String url)

Server

The server will create the service object, and register it with a name service. This registration is enough to keep the service alive.

package hello;

import java.rmi.*;

public class HelloServer {

    final static String NAME_SERVICE = "rmi://pandonia.ise.canberra.edu.au/";

    public static void main(String[] argv) {
	new HelloServer();
    }

    public HelloServer() {
	System.setSecurityManager(new RMISecurityManager());
	try {
	    HelloImpl impl = new HelloImpl();
	    Naming.rebind(NAME_SERVICE + "hello", impl);
	} catch(RemoteException e) {
	    e.printStackTrace();
	    System.exit(1);
	} catch(java.net.MalformedURLException e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }
}

Hello client

The client finds the service from the naming service, and then makes method calls on it.

package hello;

import java.rmi.*;
import java.net.MalformedURLException;

public class HelloClient {

    final static String NAME_SERVICE = "rmi://pandonia.ise.canberra.edu.au/";

    static public void main(String[] argv) {
	new HelloClient();
    }

    public HelloClient() {
	try {
	    Hello remote = (Hello) Naming.lookup(NAME_SERVICE + "hello");
	    String msg = remote.sayHello();
	    System.out.println(msg);
	} catch(NotBoundException e) {
	    e.printStackTrace();
	} catch(MalformedURLException e) {
	    e.printStackTrace();
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	} catch(RemoteException e) {
	    e.printStackTrace();
	}
    }
}

Security

JDK 1.2 introduces a comprehensive security model that grants rights to perform certain actions, according to a security policy. A security manager will implement this policy. That is, a security will allow or disallow certain actions, like writing to a file.

RMI security is about accessing objects across the network - a potentially dangerous operation. It has a security manager especially for this: RMISecurityManager. This will check all network accesses as well as file/process/etc access. A server should allow network access, but probably no other.

Access policy is given in a file, with contents

grant {
    permission java.net.SocketPermission "*:1024-65535",
                                         "connect,accept,resolve";
};
RMI calls are made on a random port above 1024, just like ONC RPC.

The policy file is specified in a call to the Java runtime

java -Djava.security.policy=policy ...

The client runs without any security manager, and can do anything.

Compilation

The hello example has four files

These can all be compiled together using
javac hello/*.java

In addition, a stub/proxy class has to be generated for HelloImpl since it implements UnicastRemoteObject. This is done by

rmic -d . -v1.2 hello.HelloImpl
Compilation and rmic result in the following class files

Running

Before anything else can start, a name server needs to run as a continuous process

    rmiregistry &

The server needs to have the following in its classpath

The server also needs to set a security policy. Then it can run as
    java -Djava.security.policy=policy hello.HelloServer

The client needs to have the following in its classpath

Then it can run as
    java hello.HelloClient

RoomBooking

The room booking service can be written under RMI. There is no need to use a CORBA IDL spec, but it is useful to repeat it:

module RoomBooking {

    interface Meeting {
    
        readonly attribute string purpose;
        readonly attribute string participants;
    };
    
    interface MeetingFactory {
        Meeting CreateMeeting( in string purpose, in string participants);
    };

    enum Slot { am9, am10, am11, pm12, pm1, pm2, pm3, pm4 };
    
    const short MaxSlots = 8;
    
    exception NoMeetingInThisSlot {};
    exception SlotAlreadyTaken {};
        
    interface Room {
        typedef Meeting Meetings[ MaxSlots ];
    
        readonly attribute string name;
    
        Meetings View();
    
        void Book( in Slot a_slot, in Meeting  a_meeting )
            raises(SlotAlreadyTaken);
    
        void Cancel( in Slot  a_slot )
            raises(NoMeetingInThisSlot);
    };
}; 
The IDL says that there will be two remote classes with their own servers Objects of Meeting will be returned as the result of method calls on MeetingFactory.

RMI supports the standard RPC mechanisms, with a service running on a server. It exports a proxy/stub that runs remotely and communicates back to the server. It also allows objects to move from one location to another: a copy of an object can be moved from one JVM to another.

The Room must export a remote reference rather than a copy. A room is a single object with state. It is defined by an interface

package room;

public interface Room extends java.rmi.Remote {

	int MAX_SLOTS = 8;

        public String getName() throws java.rmi.RemoteException;

        public Meeting[] view() throws java.rmi.RemoteException;

        public void book(int a_slot, Meeting a_meeting)
            throws SlotAlreadyTaken, java.rmi.RemoteException;                           

        public void cancel(int a_slot)
            throws NoMeetingInThisSlot, java.rmi.RemoteException;   
}
and there will be an implementation as a remote object
// RoomImpl.java
package room;

public class RoomImpl extends java.rmi.server.UnicastRemoteObject implements Room {

    private String name;
    private Meeting[] meetings;

    // constructor
    public RoomImpl() throws java.rmi.RemoteException {
	// empty
    }

    public RoomImpl( String name ) throws java.rmi.RemoteException {
        // super(name);
        this.name = name;
        meetings = new Meeting[MAX_SLOTS];
    }
    
    // attributes
    public String getName() {
        return name; 
    }
        
    // operations
    public Meeting[] view() {
        return meetings;
    }

    public void book(int slot,
        Meeting meeting )
        throws SlotAlreadyTaken {
 
        if( meetings[slot] == null ) {
                meetings[slot] = meeting;
        }
        else {
            throw new SlotAlreadyTaken();
        }        
        return;
    }

    public void cancel(int slot )
        throws NoMeetingInThisSlot {

        System.err.println("cancel " + slot );
        if( meetings[slot] != null  ) {
            meetings[slot] = null;
        }
        else {
            throw new NoMeetingInThisSlot();
        }
    }
}
The implementation is delivered from a server
package room;

import java.io.*;
import java.rmi.*;

public class RoomServer {

    final static String NAME_SERVICE = "rmi://jannote.dstc.edu.au/";

    public static void main(String[] args) {

        String context_name, str_name;

        if( args.length < 1 ) {
            System.out.println("Usage: room.RoomServer room_name");
            System.exit( 1 );
	}

	System.setSecurityManager(new RMISecurityManager());

        context_name = new String("BuildingApplications/Rooms/");

        try {
	    // create the Room object
	    String roomName = args[0];
	    RoomImpl room = new RoomImpl(roomName);

            // register with naming service
	    String path = NAME_SERVICE + context_name + roomName;
	    Naming.rebind(path, room);
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }
}

A MeetingFactory must export a remote reference rather than a copy. A meeting factory is a single object with state. A meeting factory is defined by an interface

package room;

public interface MeetingFactory extends java.rmi.Remote {

    public Meeting CreateMeeting(String purpose, String participants)
	throws java.rmi.RemoteException;
};
and has a remote object implementation
package room;

import java.rmi.*;

class MeetingFactoryImpl extends java.rmi.server.UnicastRemoteObject
                         implements MeetingFactory {


    MeetingFactoryImpl() throws RemoteException {
	// empty
    }
  
    // method
    public Meeting CreateMeeting(
        String purpose, String participants) {

        Meeting newMeeting = new Meeting(purpose, participants);

        return newMeeting; 
    }
}
A meeting factory implementation is delivered from a server
package room;

import java.io.*;
import java.rmi.*;

public class MeetingFactoryServer {

    final static String NAME_SERVICE = "rmi://jannote.dstc.edu.au/";
    final static String SERVICE = "BuildingApplications/MeetingFactories/MeetingFactory";

    public static void main(String[] args) {
	System.setSecurityManager(new RMISecurityManager());

        try {
	    // create the MeetingFactory object
	    MeetingFactoryImpl meeting_factory = new MeetingFactoryImpl();

            // register with naming service
	    Naming.rebind(NAME_SERVICE + SERVICE, meeting_factory);
        }
	catch(Exception e) {
	    System.err.println(e);
        }
    }
}

A Meeting is a readonly object. All copies of it are exactly the same, and cannot be changed because there are no setX() methods. Strictly, it should be a Java final object. So there are two possibilities

A serializable class is used here
package room;

public class Meeting implements java.io.Serializable {

    private String purpose;
    private String participants;

    public Meeting(String purpose, String participants) {
	this.purpose = purpose;
	this.participants = participants;
    }

    public String getPurpose() {
	return purpose;
    };

    public String getParticipants() {
	return participants;
    };
};

The exceptions are ordinary Java exception classes.

The relevant code from the client is

    public void init_from_ns() {

        // initialise from Naming Service
        try {
	    String[] names = Naming.list(NAME_SERVICE);
	    String roomContext = NAME_SERVICE + "BuildingApplications/Rooms/";
	    Vector roomVector = new Vector();
	    // search list of services for rooms
	    for (int n =0; n < names.length; n++) {
		if (names[n].indexOf(roomContext) != -1) {
		    // found a room
		    System.out.println("Found room " + names[n]);
		    Room room = (Room) Naming.lookup(names[n]);
		    roomVector.add(room);
		}
	    }
	    rooms = new Room[roomVector.size()];
	    roomVector.copyInto(rooms);

            // get room context
	    String str_name = "/BuildingApplications/Rooms/";
	    // build rooms here

            // get MeetingFactory from Naming Service
	    str_name = "BuildingApplications/MeetingFactories/MeetingFactory";
            meeting_factory = (MeetingFactory) Naming.lookup(NAME_SERVICE + str_name);
System.out.println("factory ok");

        } catch(Exception e) { 
	    e.printStackTrace();
	    System.exit(1);
        }
    }

Jan Newmarch (http://jan.newmarch.name)
jan@newmarch.name
Last modified: Wed May 3 12:14:11 EST 2000
Copyright ©Jan Newmarch