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.
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".
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
.
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
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"); } }
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)
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); } } }
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(); } } }
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.
The hello example has four files
Hello.java
HelloImpl.java
HelloServer.java
HelloClient.java
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.HelloImplCompilation and rmic result in the following class files
Hello.class
HelloImpl.class
HelloServer.class
HelloClient.class
HelloImpl_Stub.class
(from rmic)
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
Hello.class
HelloImpl.class
HelloServer.class
HelloImpl_Stub.class
(from rmic)
java -Djava.security.policy=policy hello.HelloServer
The client needs to have the following in its classpath
Hello.class
HelloClient.class
java hello.HelloClient
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
MeetingFactory
Room
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
UnicastRemoteObject
Serialisable
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); } }