Many of the examples in earlier chapters use RMI/Jeri proxies for services. These services 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 the proxy; if the server 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. Java memory requirements on the server side
can be enormous From JDK 1.2, there is an extension to RMI
called activation which allows an idle
object to be "dormant", and be brought to life when needed. In
this way, it does not occupy virtual memory while idle. Of
course, another process needs to be alive
to restore such objects, and RMI supplies a daemon
There are serious limitations to
If memory use was the only concern, then there are a variety
of other systems such as
The standard RMI activation system is supported by Jini 2.0,
in the same way as it supports JRMP. But with the advent of
Jeri, Jini 2.0 has a new version of activation with a new
activation server,
Phoenix replaces
Phoenix can be started by using the
host=`hostname`
java -Djava.security.manager= \
-Djava.security.policy=config/phoenix.policy \
-Djava.rmi.server.codebase=http://$host:8080/phoenix-dl.jar \
-DserverHost=$host \
-jar lib/phoenix.jar \
config/jeri-phoenix.config
and here is the batch file
java -Djava.security.manager= ^
-Djava.security.policy=config\phoenix.policy ^
-Djava.rmi.server.codebase=http://%computername%:8080/phoenix-dl.jar ^
-DserverHost=%computername% ^
-jar lib\phoenix.jar ^
config\jrmp-phoenix.config
Each script file references a configuration script. A typical script such
as
com.sun.jini.phoenix {
persistenceDirectory = "lib${/}phoenix-log";
groupConfig = new String[] { "config${/}jeri-phoenix-group.config" };
}
which states the directory to store the activation log files and also a
group configuration file such as
import com.sun.jini.phoenix.AccessILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
com.sun.jini.phoenix {
instantiatorExporter =
new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new AccessILFactory());
}
This file defines the protocol that will be used by Phoenix- here, Jeri.
There is a little trap in running Phoenix: it will create a new virtual
machine for each different group. This new virtual machine will require
the
export CLASSPATH=lib/phoenix-init.jar:$CLASSPATH
If this is not done, then when Phoenix starts, you will see errors
Group-01: class not found ActivationInitGroup
The major concepts in Activation are the activatable object
itself (which extends
Making an object into an activable object requires registering the object with the activation system by exporting it, and by using a special two-argument constructor which will be called when the object needs to be reconstructed. The constructor looks like:
public ActivatableImpl(ActivationID id, MarshalledObject data)
throws RemoteException {
...
}
(The use of the marshalled data is discussed later).
There is an important conceptual change from non-activatable services. In a non-activatable service, the server is able to create the service. In an activation system, the original server could have terminated, and will not be available to start the service. Instead, the activation server is responsible for that. But the service still has to be exported, and it can't rely on the activation server to do that (for example, it would have no knowledge of the protocol such as Jeri/JRMP/IIOP). So the service has to export itself. That is, within the constructor the service must find an exporter and export itself. This is a change from "standard" activation as used in Jini 1.2: there, many things were hidden from the programmer and it was not neccessary to pay attention to this.
That in turn raises another problem: in a non-activatable service,
the server creates the service, gets a proxy by exporting the service
and then does things like register the proxy with lookup services.
But if the export operation is buried within the
service constructor, then a
server cannot readily get access to it.
This is the role of the
With these in place, the file classifier becomes
package activation;
import net.jini.export.*;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.activation.ActivationExporter;
import net.jini.jrmp.JrmpExporter;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import net.jini.export.ProxyAccessor;
import common.MIMEType;
import common.FileClassifier;
import rmi.RemoteFileClassifier;
import java.rmi.Remote;
/**
* FileClassifierImpl.java
*/
public class FileClassifierImpl implements RemoteFileClassifier,
ProxyAccessor {
private Remote proxy;
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException {
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 activationID, MarshalledObject data)
throws java.rmi.RemoteException {
Exporter exporter =
new ActivationExporter(activationID,
new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new BasicILFactory(),
false, true));
proxy = (Remote) exporter.export(this);
}
// Implementation for ProxyAccessor
public Object getProxy() {
return proxy;
}
} // FileClassifierImpl
This listing makes explicit use of an exporter. Later we shall consider
how this could be done using a configuration.
The server doesn't actually start the service - that is the task of
a process like
Which activation group the service will belong to
The security policy to run services in a particular activation group
The classpath for
The codebase for the client to find the service (needed if the service registers itself with lookup services)
String[] options = {"-classpath",
"activation.FileClassifierServer.jar"};
CommandEnvironment commEnv =
new CommandEnvironment(null, options);
The group parameters are set using an
String[] options = {"-classpath",
"activation.FileClassifierServer.jar"};
ActivationGroupDesc.CommandEnvironment commEnv =
new CommandEnvironment(null, options);
Properties props = new Properties();
props.put("java.security.policy",
SECURITY_POLICY_FILE);
props.put("java.rmi.server.codebase",
"http://192.168.1.13/classes/activation.FileClassifierServer-dl.jar");
ActivationGroupDesc group = new ActivationGroupDesc(props, commEnv);
Note: although the classpath shown only references
the classes required for the server, in practise you may need to add in
more. For example,
The next steps are to register the group and get a group ID from that.
Then an activation description for the service is constructed. This includes
the group ID and the name of the service's class file. (Two other
parameters are discussed later.) This service can then be registered
with activate()
method.
The code for this looks like
ActivationGroupDesc group = new ActivationGroupDesc(props, commEnv);
ActivationGroupID groupID = actSys.registerGroup(group);
ActivationGroup.createGroup(groupID, group, 0);
String codebase = "...";
MarshalledObject data = null;
ActivationDesc desc = null;
desc = new ActivationDesc(groupID,
"activation.FileClassifierImpl",
codebase, data, true);
ActivationID aid = actSys.registerObject(desc);
Remote stub = (Remote) aid.activate(true);
The server now has a proxy that it can register with lookup services.
The server can now terminate, since any calls on the service will be
handled by
The file classifier server which uses an activatable service is
package activation;
//import rmi.RemoteFileClassifier;
import java.rmi.Remote;
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.rmi.activation.ActivationSystem;
import java.rmi.activation.ActivationID;
import java.util.Properties;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServer.java
*/
public class FileClassifierServer implements DiscoveryListener {
static final protected String SECURITY_POLICY_FILE =
"/home/httpd/html/java/jini/tutorial/policy.all";
// Don't forget the trailing '/'!
static final protected String CODEBASE =
"http://192.168.1.13/classes/activation.FileClassifierServer-dl.jar";
// protected FileClassifierImpl impl;
protected Remote stub;
public static void main(String argv[]) {
new FileClassifierServer(argv);
// stick around while lookup services are found
try {
Thread.sleep(100000L);
} catch(InterruptedException e) {
// do nothing
}
// the server doesn't need to exist anymore
System.exit(0);
}
public FileClassifierServer(String[] argv) {
// install suitable security manager
System.setSecurityManager(new RMISecurityManager());
// new
ActivationSystem actSys = null;
try {
actSys = ActivationGroup.getSystem();
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
// Install an activation group
Properties props = new Properties();
props.put("java.security.policy",
SECURITY_POLICY_FILE);
// props.put("java.rmi.server.codebase",
// "http://192.168.1.13/classes/activation.FileClassifierServer-dl.jar");
String[] options = {"-classpath",
"/home/httpd/html/java/jini/tutorial/dist/activation.FileClassifierServer-act.jar:/usr/local/jini2_0/lib/phoenix-init.jar:/usr/local/jini2_0/lib/jini-ext.jar"};
CommandEnvironment commEnv =
new CommandEnvironment(null, options);
System.out.println("1");
ActivationGroupDesc group = new ActivationGroupDesc(props, commEnv);
System.out.println("2");
ActivationGroupID groupID = null;
try {
groupID = actSys.registerGroup(group);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
String codebase = CODEBASE;
MarshalledObject data = null;
ActivationDesc desc = null;
desc = new ActivationDesc(groupID,
"activation.FileClassifierImpl",
codebase, data, true);
// new
ActivationID aid = null;
try {
aid = actSys.registerObject(desc);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
System.out.println("3");
stub = (Remote) aid.activate(true);
System.out.println("4 " + stub);
// 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();
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) {
}
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServer
The service and the server must be compiled as usual.
Nonactivatable services just require classes for the client
and for the server. For activatable services it is more
complex: classes are required for the client, for the
startup server and for
The classes that are required by the client must be
copied to an http server. In this case, it is only the
class file
The classes needed by the startup server are the file
classifier server and the classes it needs.
This gets a bit tricky. The server doesn't actually
create the service at any time, so it doesn't need the
class file for
common/MIMEType.class
common/FileClassifier.class
rmi/RemoteFileClassifier.class
activation/FileClassifierServer.class
Finally, the classes needed by
FileClassifierImpl
and its dependencies,
but not the startup server
common/MIMEType.class
common/FileClassifier.class
rmi/RemoteFileClassifier.class
activation/FileClassifierImpl.class
Before starting the service provider, a
codebase
property on the service. The service
provider can then be started. This will register the service
with
In summary, there are typically three processes involved in getting an activatable service running
The service provider, which specifies information
about the service to
An HTTP server, which can be on a different machine, and is pointed to by the codebase
While the service remains registered with lookup services,
clients can download its proxy. The service will be created
on demand by
The type of service discussed above are ``lazy'' services,
activated on demand when their methods are called. This
reduces memory use, at the expense of starting up a new JVM
when required. Some services need to be continuously alive,
but can still benefit from the log mechanism of
An activatable object is created afresh each time a method is
called on it, using its two argument constructor. This will
result in the object being created in the same state on each
activation. However, method calls on objects (apart from
get...()
methods) usually result in a change of
state of the object. Activatable objects will need some way of
reflecting this change on each activation, and this is
typically done by saving and restoring state using a disk
file.
When an object is activated, one of the parameters passed to
it is a ActivationSystem.registerObject()
. This object
does not change between different activations. So it cannot
hold changing state, but only data which is fixed for all
activations. A simple use for it is to hold the name of a file
that can be used for state. Then on each activation, the
object can restore state by reading stored information. On
each subsequent method call that changes state, the
information in the file can be overwritten.
The "mutable file classifier" was discussed in an earlier
chapter, which could be sent addType()
and
removeType()
messages. It begins with a given set
of MIME type/file extension mappings. State here is very
simple, just storing all the file extensions and their
corresponding MIME type in a Map
. If we turn this
into an activatable object, we store the state by just storing
the map. This can be saved to disk using
ObjectOutputStream.writeObject()
, and retrieved
by ObjectInputStream.readObject()
. More complex
cases might need more complex storage methods.
The very first time a mutable file classifier starts on a
particular host, it should build its initial state file. There
are a variety of methods that could be used. For example, if
the state file does not exist, then the first activation could
detect this and construct the initial state at that time.
Alternatively, a method such as init()
could be
defined, to be called once after the object has been
registered with the activation system.
The "normal" way of instantiating an object - through a
constructor - doesn't work too well with activatable objects.
If a constructor for a class doesn't start by calling another
constructor by this(...)
or
super(...)
, then the no argument superclass
constructor super()
is called. But the class
FileClassifierMutable(String
stateFile)
that doesn't use the activation system.
You can avoid this by not inheriting from
public FileClassifierMutable(ActivationID id,
MarshalledObject data) throws java.rmi.RemoteException {
Activatable.exportObject(this, id, 0); // continue with
instantiation
Nevertheless, this is a bit
clumsy in use: you create an object solely to build up initial
state, and then discard it since the activation system will
recreate it on demand.
The technique adopted in this example is to create initial
state if the attempt to restore state from the state file
fails for any reason as the object is activated. This is done
in the method restoreMap()
, called from the
constructor FileClassifierMutable(ActivationID id,
MarshalledObject data)
. The name of the file is
extracted from the marshalled object passed in as parameter.
package activation;
import java.io.*;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import java.rmi.Remote;
import java.rmi.activation.ActivationID;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.EventRegistration;
import java.rmi.RemoteException;
import net.jini.core.event.UnknownEventException ;
import net.jini.export.ProxyAccessor;
import net.jini.export.*;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.activation.ActivationExporter;
import javax.swing.event.EventListenerList;
import common.MIMEType;
import common.MutableFileClassifier;
import mutable.RemoteFileClassifier;
import java.util.Map;
import java.util.HashMap;
/**
* FileClassifierMutable.java
*/
public class FileClassifierMutable implements RemoteFileClassifier,
ProxyAccessor {
private Remote proxy;
/**
* Map of String extensions to MIME types
*/
private Map map = new HashMap();
/**
* Permanent storage for the map while inactive
*/
private String mapFile;
/**
* Listeners for change events
*/
private EventListenerList listenerList = new EventListenerList();
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException {
System.out.println("Called with " + fileName);
MIMEType type;
String fileExtension;
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex == -1 || dotIndex + 1 == fileName.length()) {
// can't find suitable suffix
return null;
}
fileExtension= fileName.substring(dotIndex + 1);
type = (MIMEType) map.get(fileExtension);
return type;
}
public void addType(String suffix, MIMEType type)
throws java.rmi.RemoteException {
map.put(suffix, type);
fireNotify(MutableFileClassifier.ADD_TYPE);
saveMap();
}
public void removeMIMEType(String suffix, MIMEType type)
throws java.rmi.RemoteException {
if (map.remove(suffix) != null) {
fireNotify(MutableFileClassifier.REMOVE_TYPE);
saveMap();
}
}
public EventRegistration addRemoteListener(RemoteEventListener listener)
throws java.rmi.RemoteException {
listenerList.add(RemoteEventListener.class, listener);
return new EventRegistration(0, this, null, 0);
}
// Notify all listeners that have registered interest for
// notification on this event type. The event instance
// is lazily created using the parameters passed into
// the fire method.
protected void fireNotify(long eventID) {
RemoteEvent remoteEvent = null;
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == RemoteEventListener.class) {
RemoteEventListener listener = (RemoteEventListener) listeners[i+1];
if (remoteEvent == null) {
remoteEvent = new RemoteEvent(this, eventID,
0L, null);
}
try {
listener.notify(remoteEvent);
} catch(UnknownEventException e) {
e.printStackTrace();
} catch(RemoteException e) {
e.printStackTrace();
}
}
}
}
/**
* Restore map from file.
* Install default map if any errors occur
*/
public void restoreMap() {
try {
FileInputStream istream = new FileInputStream(mapFile);
ObjectInputStream p = new ObjectInputStream(istream);
map = (Map) p.readObject();
istream.close();
} catch(Exception e) {
e.printStackTrace();
// restoration of state failed, so
// load a predefined set of MIME type mappings
map.put("gif", new MIMEType("image", "gif"));
map.put("jpeg", new MIMEType("image", "jpeg"));
map.put("mpg", new MIMEType("video", "mpeg"));
map.put("txt", new MIMEType("text", "plain"));
map.put("html", new MIMEType("text", "html"));
this.mapFile = mapFile;
saveMap();
}
}
/**
* Save map to file.
*/
public void saveMap() {
try {
FileOutputStream ostream = new FileOutputStream(mapFile);
ObjectOutputStream p = new ObjectOutputStream(ostream);
p.writeObject(map);
p.flush();
ostream.close();
} catch(Exception e) {
e.printStackTrace();
}
}
public FileClassifierMutable(ActivationID activationID,
MarshalledObject data)
throws java.rmi.RemoteException {
Exporter exporter =
new ActivationExporter(activationID,
new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new BasicILFactory(),
false, true));
proxy = (Remote) exporter.export(this);
try {
mapFile = (String) data.get();
} catch(Exception e) {
e.printStackTrace();
}
restoreMap();
}
// Implementation for ProxyAccessor
public Object getProxy() {
return proxy;
}
} // FileClassifierMutable
The difference between the server for this service and the
last one, is that we now have to prepare a marshalled object
for the state file, and register this with the activation
system. Here the filename is hard-coded, but it could be given
as a command-line argument (like services such as
package activation;
//import rmi.RemoteFileClassifier;
import java.rmi.Remote;
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.rmi.activation.ActivationSystem;
import java.rmi.activation.ActivationID;
import java.util.Properties;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServer.java
*/
public class FileClassifierServerMutable implements DiscoveryListener {
static final protected String SECURITY_POLICY_FILE =
"/home/httpd/html/java/jini/tutorial/policy.all";
// Don't forget the trailing '/'!
static final protected String CODEBASE =
"http://192.168.1.13/classes/activation.FileClassifierServer-dl.jar";
static final protected String LOG_FILE = "/tmp/file_classifier";
// protected FileClassifierImpl impl;
protected Remote stub;
public static void main(String argv[]) {
new FileClassifierServerMutable(argv);
// stick around while lookup services are found
try {
Thread.sleep(100000L);
} catch(InterruptedException e) {
// do nothing
}
// the server doesn't need to exist anymore
System.exit(0);
}
public FileClassifierServerMutable(String[] argv) {
// install suitable security manager
System.setSecurityManager(new RMISecurityManager());
// new
ActivationSystem actSys = null;
try {
actSys = ActivationGroup.getSystem();
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
// Install an activation group
Properties props = new Properties();
props.put("java.security.policy",
SECURITY_POLICY_FILE);
// props.put("java.rmi.server.codebase",
// "http://192.168.1.13/classes/activation.FileClassifierServer-dl.jar");
String[] options = {"-classpath",
"/home/httpd/html/java/jini/tutorial/dist/activation.FileClassifierServer-act.jar:/usr/local/jini2_0/lib/phoenix-init.jar:/usr/local/jini2_0/lib/jini-ext.jar"};
CommandEnvironment commEnv =
new CommandEnvironment(null, options);
System.out.println("1");
ActivationGroupDesc group = new ActivationGroupDesc(props, commEnv);
System.out.println("2");
ActivationGroupID groupID = null;
try {
groupID = actSys.registerGroup(group);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
String codebase = CODEBASE;
MarshalledObject data = null;
// set a log file for the service
try {
data = new MarshalledObject(LOG_FILE);
} catch(java.io.IOException e) {
e.printStackTrace();
}
ActivationDesc desc = null;
desc = new ActivationDesc(groupID,
"activation.FileClassifierImpl",
codebase, data, true);
// new
ActivationID aid = null;
try {
aid = actSys.registerObject(desc);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
System.out.println("3");
stub = (Remote) aid.activate(true);
System.out.println("4 " + stub);
// 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();
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) {
}
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServer
This example used a simple way to store state. Sun uses a far
more complex system in many of its services such as
com.sun.jini.reliableLog
. However, this
package is not a part of standard Jini, so may change or even
be removed in later versions of Jini. However, there is
nothing to stop you using it if you need a robust storage
mechanism.
The service implementations shown in this chapter have hard-coded protocol - Jeri. In general this is not a good idea, as it should be left to a runtime configuration to specify this. So the code to find an exporter should be handled by looking in a configuration, as we did in the Configuration chapter.
The startup server is the one that will see the configuration file, typically a filename as a command line parameter. For non-activable services the server is able to extract the exporte directly from the configuration and use it to export the service. But as we have seen, it is now the responsibility of the service itself to define and use an exporter. The problem is how to get the command line parameters from the startup server into the service's constructor.
This can be solved by using the marshalled data discussed in the last section: but instead of using it for state, we can place the command line arguments from the server into this and so pass the configuration into the client.
Activatable objects are one example of services that are not continuously alive. Mobile services, such as those that will exist on mobile phones, are another. These services will be brought to life on demand (as activatable objects), or will join the network on occasions. These services raise a number of problems, and one was skirted around in the last section: how to renew leases when the object is not alive?
Activatable objects are brought back to life when methods are invoked on
them. The expiration of a lease does not cause any methods to be invoked.
There is no ``lease expirating event'' generated that could cause a listener
method to be invoked, either. It is true that a
If a server is alive, then it can use a
Jini 1.1 supplies a lease renewal service that partly avoids
these problems. Since it runs as a service, it has an independent existence
that does not depend on the server for any other service. It can act like
a
There is a small hiccup in this: how long should the
But this mechanism won't work
for
Activatable services can only be woken by calling one of their methods.
The notify()
method on a listener. If the
listener is the activatable object, this will wake it up so that
it can perform the renewal. If the
This is not quite satisfactory for other types of ``dormant'' services such
as might exist on mobile phones, since there is no equivalent of
Jini 1.1 supplies an implementation of
java -jar [setup_jvm_options] executable_jar_file
codebase_arg norm_policy_file_arg
log_directory_arg
[groups] [server_jvm] [server_jvm_args]
as in
java -jar \
-Djava.security.policy=/files/jini1_1/example/txn/policy.all \
/files/jini1_1/lib/norm.jar \
http://`hostname`:8080/norm-dl.jar \
/files/jini1_1/example/books/policy.all /tmp/norm_log
The first security file defines the policy that will be used for the server startup.
The file norm.jar
contains the class files for the norm-dl.jar
. The second security file defines the
policy file that will be used in execution of the
The
The
package net.jini.lease;
public interface LeaseRenewalService {
LeaseRenewalSet createLeaseRenewalSet(long leaseDuration)
throws java.rmi.RemoteException;
}
The
The createLeaseRenewalSet
has interface including
package net.jini.lease;
public interface LeaseRenewalSet {
public void renewFor(Lease leaseToRenew,
long membershipDuration)
throws RemoteException;
public EventRegistration setExpirationWarningListener(
RemoteEventListener listener,
long minWarning,
MarshalledObject handback)
throws RemoteException;
....
}
The renewFor()
method adds a new lease to the set being
looked after. The membershipDuration
expires or the
lease on the whole
Setting an expiration warning listener means that its notify()
method will be called at least minWarning
millseconds before
the lease for the set expires. The event argument to this will actually be
an
package net.jini.lease;
public class ExpirationWarningEvent extends RemoteEvent {
Lease getLease();
}
This allows the listener to get the lease for the
/**
* @version 1.1
*/
package activation;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.lease.ExpirationWarningEvent;
public class RenewLease extends Activatable
implements RemoteEventListener {
public RenewLease(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException {
super(id, 0);
}
public void notify(RemoteEvent evt) {
System.out.println("expiring... " + evt.toString());
ExpirationWarningEvent eevt = (ExpirationWarningEvent) evt;
Lease lease = eevt.getRenewalSetLease();
try {
// This is short, for testing. Try 2+ hours
lease.renew(20000L);
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("Lease renewed for " +
(lease.getExpiration() -
System.currentTimeMillis()));
}
}
The server will need to register the service and export it as an activatable object. This is done in exactly the same way as in the first example of this chapter. In addition
It will also need to register the lease listener with the activation system as an activatable object
It will need to find a
It will need to register all leases from lookup services with the
package activation;
import rmi.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.lookup.ServiceTemplate;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.lease.LeaseRenewalService;
import net.jini.lease.LeaseRenewalSet;
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.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import java.util.Properties;
import java.util.Vector;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServer.java
*/
public class FileClassifierServerLease
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 RemoteFileClassifier stub;
protected RemoteEventListener leaseStub;
// Lease renewal management
protected LeaseRenewalSet leaseRenewalSet = null;
// List of leases not yet managed by a LeaseRenewalService
protected Vector leases = new Vector();
public static void main(String argv[]) {
new FileClassifierServerLease(argv);
// stick around while lookup services are found
try {
Thread.sleep(10000L);
} catch(InterruptedException e) {
// do nothing
}
// the server doesn't need to exist anymore
System.exit(0);
}
public FileClassifierServerLease(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;
ActivationDesc descLease = null;
try {
desc = new ActivationDesc("activation.FileClassifierImpl",
codebase, data);
descLease = new ActivationDesc("activation.RenewLease",
codebase, data);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
stub = (RemoteFileClassifier) Activatable.register(desc);
leaseStub = (RemoteEventListener) Activatable.register(descLease);
} 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) {
}
Lease lease = reg.getLease();
// if we have a lease renewal manager, use it
if (leaseRenewalSet != null) {
try {
leaseRenewalSet.renewFor(lease, Lease.FOREVER);
} catch(RemoteException e) {
e.printStackTrace();
}
} else {
// add to the list of unmanaged leases
leases.add(lease);
// see if this lookup service has a lease renewal manager
findLeaseService(registrar);
}
}
}
public void findLeaseService(ServiceRegistrar registrar) {
System.out.println("Trying to find a lease service");
Class[] classes = {LeaseRenewalService.class};
ServiceTemplate template = new ServiceTemplate(null, classes,
null);
LeaseRenewalService leaseService = null;
try {
leaseService = (LeaseRenewalService) registrar.lookup(template);
} catch(RemoteException e) {
e.printStackTrace();
return;
}
if (leaseService == null) {
System.out.println("No lease service found");
return;
}
try {
// This time is unrealistically small - try 10000000L
leaseRenewalSet = leaseService.createLeaseRenewalSet(20000);
System.out.println("Found a lease service");
// register a timeout listener
leaseRenewalSet.setExpirationWarningListener(leaseStub, 5000,
null);
// manage all the leases found so far
for (int n = 0; n < leases.size(); n++) {
Lease ll = (Lease) leases.elementAt(n);
leaseRenewalSet.renewFor(ll, Lease.FOREVER);
}
leases = null;
} catch(RemoteException e) {
e.printStackTrace();
}
Lease renewalLease = leaseRenewalSet.getRenewalSetLease();
System.out.println("Lease expires in " +
(renewalLease.getExpiration() -
System.currentTimeMillis()));
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServerLease
It is easy enough for a server to discover all of the lookup services
within reach at the time it is started, using
Jini 1.1 supplies a service, the rmid
.
When there is a change to lookup services, the
The
package
public interface LookupDiscoveryService {
LookupDiscoveryRegistration register(String[] groups,
LookupLocator[] locators,
RemoteEventListener listener,
MarshalledObject handback,
long leaseDuration);
}
Calling the register()
method will begin a multicast search
for the groups, and unicast lookup for the locators. The registration is
leased, and will need to be renewed before expiry (a lease
renewal service can be used for this). Note that the listener
cannot be null
- this is simple
sanity checking, for if the listener was null
then the service
could never do anything useful!
A lookup service in one of the groups can start or terminate. Or it can
change its group membership in such a way that it now does (or doesn't)
meet the group criteria. A lookup service in the locators list can
also start or stop. These will generate notify()
method of the listener. The event interface
includes
package net.jini.discovery;
public interface RemoteDiscoveryEvent {
ServiceRegistrar[] getRegistrars();
boolean isDiscarded();
...
}
The list of registrars is the set that triggered the event. The
isDiscarded()
method is used to check if it is a ``discovered''
lookup service or a ``discarded'' lookup service.
An initial event is not posted when the listener is registered: the set
of lookup services that are initially found can be retrieved from the
register()
method, by its getRegistrars()
.
The Jini 1.1 release includes an implementation of the lookup discovery
service, called fiddler
. This is started in a similar
way to other services such as
java -jar [setup_jvm_options] executable_jar_file
codebase_arg fiddler_policy_file_arg
log_directory_arg
[groups and
locators] [server_jvm] [server_jvm_args]
as in
java -jar \
-Djava.security.policy=/files/jini1_1/example/txn/policy.all \
/files/jini1_1/lib/fiddler.jar \
http://`hostname`:8080/norm-dl.jar \
/files/jini1_1/example/books/policy.all /tmp/fiddler_log
WARNING: the program in this section does not yet work, I think due to a bug in Jini 1.1 (alpha). An activatable service can make use of a lease renewal service to look after the leases for lookup services discovered. How it finds these lookup services can be by means of a lookup discovery service. The logic to manage these two services is a little tricky.
While lease management can be
done by the lease renewal service, the lease renewal set will also be leased
and will need to be renewed on occasions. The lease renewal service can call
an activatable
The lookup discovery service is also a leased service - it will only report changes to lookup services while its own lease is current. So the lease from this service will have to be managed by the lease renewal service, in addition to the leases for any lookup services discovered.
The primary purpose of the lookup discovery service is to call the
notify()
method of some object when information about lookup
services changes. This object should also be an activatable object.
We define a notify()
to handle changes in lookup services. If a lookup service has disappeared, we don't
worry about it. If a lookup service has been discovered, we want to register the
service with it, and then manage the resultant lease. This means that the
package activation;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.lease.ExpirationWarningEvent;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.lease.LeaseRenewalSet;
import net.jini.discovery.RemoteDiscoveryEvent;
import java.rmi.RemoteException;
import net.jini.discovery.LookupUnmarshalException;
import rmi.RemoteFileClassifier;
public class DiscoveryChange extends Activatable
implements RemoteEventListener {
protected LeaseRenewalSet leaseRenewalSet;
protected RemoteFileClassifier service;
public DiscoveryChange(ActivationID id, MarshalledObject data)
throws java.rmi.RemoteException {
super(id, 0);
Object[] objs = null;
try {
objs = (Object []) data.get();
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(java.io.IOException e) {
e.printStackTrace();
}
service = (RemoteFileClassifier) objs[0];
leaseRenewalSet= (LeaseRenewalSet) objs[1];
}
public void notify(RemoteEvent evt) {
System.out.println("lookups changing... " + evt.toString());
RemoteDiscoveryEvent revt = (RemoteDiscoveryEvent) evt;
if (! revt.isDiscarded()) {
// The event is a discovery event
ServiceItem item = new ServiceItem(null, service, null);
ServiceRegistrar[] registrars = null;
try {
registrars = revt.getRegistrars();
} catch(LookupUnmarshalException e) {
e.printStackTrace();
return;
}
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
leaseRenewalSet.renewFor(reg.getLease(), Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.println("Register exception: " + e.toString());
}
}
}
}
}
The server must install an activation group, and then find activation proxies
for the service itself and also for our lease renewal object. After this, it can
use a
package activation;
import rmi.RemoteFileClassifier;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryService;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.discovery.LookupDiscoveryRegistration;
import net.jini.discovery.LookupUnmarshalException;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.lease.LeaseRenewalService;
import net.jini.lease.LeaseRenewalSet;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceDiscoveryManager;
import java.rmi.RMISecurityManager;
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.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
import java.util.Properties;
import java.util.Vector;
/**
* FileClassifierServerDiscovery.java
*/
public class FileClassifierServerDiscovery
/* implements DiscoveryListener */ {
private static final long WAITFOR = 10000L;
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 RemoteFileClassifier serviceStub;
protected RemoteEventListener leaseStub,
discoveryStub;
// Services
protected LookupDiscoveryService discoveryService = null;
protected LeaseRenewalService leaseService = null;
// Lease renewal management
protected LeaseRenewalSet leaseRenewalSet = null;
// List of leases not yet managed by a LeaseRenewalService
protected Vector leases = new Vector();
protected ServiceDiscoveryManager clientMgr = null;
public static void main(String argv[]) {
new FileClassifierServerDiscovery();
// stick around while lookup services are found
try {
Thread.sleep(20000L);
} catch(InterruptedException e) {
// do nothing
}
// the server doesn't need to exist anymore
System.exit(0);
}
public FileClassifierServerDiscovery() {
// install suitable security manager
System.setSecurityManager(new RMISecurityManager());
installActivationGroup();
serviceStub = (RemoteFileClassifier)
registerWithActivation("activation.FileClassifierImpl", null);
leaseStub = (RemoteEventListener)
registerWithActivation("activation.RenewLease", null);
initClientLookupManager();
findLeaseService();
// the discovery change listener needs to know the service and the lease service
Object[] discoveryInfo = {serviceStub, leaseRenewalSet};
MarshalledObject discoveryData = null;
try {
discoveryData = new MarshalledObject(discoveryInfo);
} catch(java.io.IOException e) {
e.printStackTrace();
}
discoveryStub = (RemoteEventListener)
registerWithActivation("activation.DiscoveryChange",
discoveryData);
findDiscoveryService();
}
public void installActivationGroup() {
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);
}
}
public Object registerWithActivation(String className, MarshalledObject data) {
String codebase = CODEBASE;
ActivationDesc desc = null;
Object stub = null;
try {
desc = new ActivationDesc(className,
codebase, data);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
stub = 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);
}
return stub;
}
public void initClientLookupManager() {
LookupDiscoveryManager lookupDiscoveryMgr = null;
try {
lookupDiscoveryMgr =
new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
null /* unicast locators */,
null /* DiscoveryListener */);
clientMgr = new ServiceDiscoveryManager(lookupDiscoveryMgr,
new LeaseRenewalManager());
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void findLeaseService() {
leaseService = (LeaseRenewalService) findService(LeaseRenewalService.class);
if (leaseService == null) {
System.out.println("Lease service null");
}
try {
leaseRenewalSet = leaseService.createLeaseRenewalSet(20000);
leaseRenewalSet.setExpirationWarningListener(leaseStub, 5000,
null);
} catch(RemoteException e) {
e.printStackTrace();
}
}
public void findDiscoveryService() {
discoveryService = (LookupDiscoveryService) findService(LookupDiscoveryService.class);
if (discoveryService == null) {
System.out.println("Discovery service null");
}
LookupDiscoveryRegistration registration = null;
try {
registration =
discoveryService.register(LookupDiscovery.ALL_GROUPS,
null,
discoveryStub,
null,
Lease.FOREVER);
} catch(RemoteException e) {
e.printStackTrace();
}
// manage the lease for the lookup discovery service
try {
leaseRenewalSet.renewFor(registration.getLease(), Lease.FOREVER);
} catch(RemoteException e) {
e.printStackTrace();
}
// register with the lookup services already found
ServiceItem item = new ServiceItem(null, serviceStub, null);
ServiceRegistrar[] registrars = null;
try {
registrars = registration.getRegistrars();
} catch(RemoteException e) {
e.printStackTrace();
return;
} catch(LookupUnmarshalException e) {
e.printStackTrace();
return;
}
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
leaseRenewalSet.renewFor(reg.getLease(), Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.println("Register exception: " + e.toString());
}
}
}
public Object findService(Class cls) {
Class [] classes = new Class[] {cls};
ServiceTemplate template = new ServiceTemplate(null, classes,
null);
ServiceItem item = null;
try {
item = clientMgr.lookup(template,
null, /* no filter */
WAITFOR /* timeout */);
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
if (item == null) {
// couldn't find a service in time
System.out.println("No service found for " + cls.toString());
return null;
}
return item.service;
}
} // FileClassifierServerDiscovery
Some objects may not always be available, either because of mobility issues or because they are activatable objects. This chapter has dealt with activatable objects, and also with some of the special services that are needed to properly support these transient objects.
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.