Contents
Many of the examples in earlier chapters
use RMI proxies for services. These services subclass
UnicastRemoteObject
, and live within a server whose principal task
is to keep the service alive and registered with lookup services. If the server
fails to renew leases then lookup services will eventually discard it; if it
fails to keep itself and its service alive then the service will not be available
when a client wants to use it.
This results in a server and a service which most of the time will be idle,
probably swapped out to disk but still using virtual memory. In JDK 1.2,
the memory requirements can be enormous (hopefully this will get fixed, but at
the moment this is a severe embarrassment to Java, and a potential threat to the
success of Jini). In JDK 1.2, there is an extension to RMI called
activation which allows an idle object to die, and be
recalled to life when needed. In this way, it does not occupy virtual memory
while idle. Of course, a process needs to be alive to restore such objects,
and RMI supplies a daemon rmid
to manage this. In effect,
rmid
acts as another virtual memory manager as it stores information
about dormant Java objects in its own files and restores them from there as
needed.
There is a serious limitation to rmid
: it is a Java program itself
and when running also uses enormous amounts of memory! So it only makes sense to
use this technique when you expect to be running a number of largely idle services
on the same machine. When a service is recalled to life,
or activated, a new JVM may be started to run the object.
This again increases memory use.
If memory use was the only concern, then there are a variety of other systems
such as echidna
which run multiple applications within a single
JVM. These may be adequate to solve memory issues.
However, RMI Activation is also designed to work with distributed objects,
and allows JVM's to hold remote references to objects which are no longer
active. Instead of throwing a remote exception on trying to access these
objects, the Activation system tries to resurrect the object using
rmid
to give a valid (and new) reference. Of course, if it
fails to do this, it will throw an exception anyway.
The principal place that this is used in the standard Jini distribution is with
the reggie
lookup service. reggie
is an activatable
service that starts, registers itself with rmid
and then exits.
Whenever lookup services are required, rmid
restarts reggie
in a new JVM. Clients of the lookup service are unaware of this mechanism:
they simply make calls on their proxy ServiceRegistration
object
and the Activation system looks after the rest. The main problem is for the
system administrator: getting reggie
to work in the first place!
The major concepts in Activation are the activatable object itself
(which extends java.rmi.activation.Activatable
) and
the environment in which it runs, an ActivationGroup
. A JVM
may have an activation group associated with it. If an object needs to be
activated and there is already a JVM running its group then it is restarted
within that JVM. Otherwise, a new JVM is started. An activation group may hold
a bunch of co-operating objects (I think - check this!!!).
Making an object into an activable object requires subclassing from
Activatable
, and using a special two-argument constructor
which will be called when the object needs to be reconstructed.
There is a standard implementation of this constructor which just
passes up to the super class:
public ActivatableImpl(ActivationID id, MarshalledObject data)
throws RemoteException {
super(id, 0);
}
(The marshalled data is used for what???).
This is the only change normally needed for a service. For example,
the file classifier becomes
package activation;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import common.MIMEType;
import common.FileClassifier;
import option3.RemoteFileClassifier;
/**
* FileClassifierImpl.java
*
*
* Created: Wed Mar 17 14:22:13 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class FileClassifierImpl extends Activatable
implements RemoteFileClassifier {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException {
System.out.println("Called with " + fileName);
try {Runtime.getRuntime().exec("/usr/bin/whoami");} catch(Exception e){e.printStackTrace();}
java.util.Properties props = System.getProperties();
System.out.println("user " + props.getProperty("user.name"));
if (new java.io.File("/etc/at.deny").canRead())
System.out.println("can read at.deny");
else
System.out.println("can't read at.deny");
if (fileName.endsWith(".gif")) {
return new MIMEType("image", "gif");
} else if (fileName.endsWith(".jpeg")) {
return new MIMEType("image", "jpeg");
} else if (fileName.endsWith(".mpg")) {
return new MIMEType("video", "mpeg");
} else if (fileName.endsWith(".txt")) {
return new MIMEType("text", "plain");
} else if (fileName.endsWith(".html")) {
return new MIMEType("text", "html");
} else
// fill in lots of other types,
// but eventually give up and
return new MIMEType(null, null);
}
public FileClassifierImpl(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException {
super(id, 0);
}
} // FileClassifierImpl
The server needs to create an activation group for the objects to run in.
The main issue in this is to set a security policy file. There are two
security policies in activatable objects: the policy used to
create the service
and export it, and the policy used to restore it.
The activation group uses its
policy file for restoration.
The policy file for initial creation is set
using the normal -Djava.security.policy=...
argument to starting
the server. After setting various parameters, the activation group is set for the JVM
by ActivationGroup.createGroup()
.
The ``standard'' remote objects that subclass UnicastRemoteObject
are created in the normal way using a constructor. Activatable objects are
not constructed in the server but are instead registered with rmid
,
which will look after construction on an as-needed basis. rmid
needs to know the class name and the location of the class files.
These are wrapped up in an ActivationDesc
, and registered with
rmid
by Activatable.register()
. This returns a
stub object that can be registered with lookup services using the
ServiceRegistrar.register()
methods.
The changes for servers from the unicast remote objects are
rmid
The file classifier server which uses an activatable service is
package activation;
import option3.RemoteFileClassifier;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import java.rmi.RMISecurityManager;
import java.rmi.MarshalledObject;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.util.Properties;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServer.java
*
*
* Created: Wed Dec 22 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class FileClassifierServer implements DiscoveryListener {
static final protected String SECURITY_POLICY_FILE =
"/home/jan/projects/jini/doc/policy.all";
// Don't forget the trailing '/'!
static final protected String CODEBASE = "http://localhost/classes/";
// protected FileClassifierImpl impl;
protected RemoteFileClassifier stub;
public static void main(String argv[]) {
new FileClassifierServer(argv);
}
public FileClassifierServer(String[] argv) {
// install suitable security manager
System.setSecurityManager(new RMISecurityManager());
// Install an activation group
Properties props = new Properties();
props.put("java.security.policy",
SECURITY_POLICY_FILE);
ActivationGroupDesc.CommandEnvironment ace = null;
ActivationGroupDesc group = new ActivationGroupDesc(props, ace);
ActivationGroupID groupID = null;
try {
groupID = ActivationGroup.getSystem().registerGroup(group);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
ActivationGroup.createGroup(groupID, group, 0);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
String codebase = CODEBASE;
MarshalledObject data = null;
ActivationDesc desc = null;
try {
desc = new ActivationDesc("activation.FileClassifierImpl",
codebase, data);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
System.out.println("Group ID " + ActivationGroup.currentGroupID().toString());
try {
stub = (RemoteFileClassifier) Activatable.register(desc);
} catch(UnknownGroupException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
}
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
RemoteFileClassifier service;
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
// export the proxy service
ServiceItem item = new ServiceItem(null,
stub,
null);
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.print("Register exception: ");
e.printStackTrace();
// System.exit(2);
continue;
}
try {
System.out.println("service registered at " +
registrar.getLocator().getHost());
} catch(Exception e) {
} System.out.println("exiting...");
System.exit(0);
// leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServer
The service and the server must be compiled as usual, and in addition an
RMI stub object must be created for the service using rmic
.
The class files for the stub must be copied to somewhere that an HTTP server
can deliver them to clients. This is the same as for any other RMI stubs.
There is an extra step that must be performed for Activatable objects:
the activation server rmid
must be able to reconstruct a
copy of the service (the client must be able to
reconstruct a copy the services's stub). This means that
rmid
must have access to the class files, either from an
HTTP server or from the file system. In the example, the codebase
property in the ActivationDesc
is set to an HTTP address, so the
class files for the service must be accessible to an HTTP server.
The server need not be on the same machine as the service.
Before starting the server, an rmid
process must be set running.
on the same machine as the service. An HTTP server must be running
on a machine as specified by the codebase
property on the
service. The server can then be started. This will register the service with
rmid
, and will copy a stub object up to any lookup services that
are found. The server can then terminate.
While the service remains registered with lookup services, clients can download
its RMI stub. The service will be created on demand by rmid
.
Note that the JVM for the service will be created as needed by rmid
,
and will be running in the same environment as rmid
. In particular,
such things as the current directory for the service will the same as for rmid
,
not from where the server ran. Similarly, the user id for the service will be the
user id of rmid
. This may lead to some access problems in a multiuser
environment - and you should never run rmid
with superuser privileges,
since this will give access to your entire system! For example, a hostile service
running on your machine could read the shadow password file, or worse.