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
several files such as
Group-01: class not found ActivationInitGroup
The Sun documentation recommends including
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";
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
An Ant file to build, deploy and run the service (but not
phoenix
) is
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
An Ant file for this is
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 so far have hard-coded the 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. Previously, for non-activable services the server is able to extract the exporter 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.
The changes to the service are to add in configuration code to the constructor
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 net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import common.MIMEType;
import common.FileClassifier;
import rmi.RemoteFileClassifier;
import java.rmi.Remote;
/**
* FileClassifierConfig.java
*/
public class FileClassifierConfig 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 FileClassifierConfig(ActivationID activationID, MarshalledObject data)
throws java.rmi.RemoteException {
// The marshalled object should be an array of strings
// holding a configuration
String[] args = null;
try {
args = (String[]) data.get();
} catch(Exception e) {
// empty
}
Exporter defaultExporter =
new ActivationExporter(activationID,
new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new BasicILFactory(),
false, true));
Exporter exporter = defaultExporter;
try {
Configuration config = ConfigurationProvider.getInstance(args);
exporter = (Exporter) config.getEntry( "JeriExportDemo",
"exporter",
Exporter.class);
} catch(ConfigurationException e) {
// empty
}
proxy = (Remote) exporter.export(this);
}
// Implementation for ProxyAccessor
public Object getProxy() {
return proxy;
}
} // FileClassifierImpl
The startup server marshalls the command line arguments and passes them into the activation description
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.io.IOException;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServerConfig.java
*/
public class FileClassifierServerConfig 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 FileClassifierServerConfig(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 FileClassifierServerConfig(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;
// marshall the command line args for the service
try {
data = new MarshalledObject(argv);
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
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
An Ant file for this is
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 expiring 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 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 supplied an implementation of
JRMP
transient
persistent
activatable (requires an activation server such
as
Jeri
transient
persistent
activatable (requires an activation server such
as
For example, to run the transient Jeri version
java -Djava.security.policy=config_dir/jsk-all.policy \
-jar install_dir/lib/start.jar \
config_dir/start-transient-norm.config
for suitable values of The policy file could contain
grant codebase "file:install_dir/lib/*" {
permission java.security.AllPermission;
};
The file
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.start.ServiceDescriptor;
com.sun.jini.start {
private static codebase = "http://your_host:http_port/norm-dl.jar";
private static policy = "config_dir/jsk-all.policy";
private static classpath = "install_dir/lib/norm.jar";
private static config = "config_dir/transient-norm.config";
static serviceDescriptors = new ServiceDescriptor[] {
new NonActivatableServiceDescriptor(
codebase, policy, classpath,
"com.sun.jini.norm.TransientNormServerImpl",
new String[] { config })
};
}
This points to the
com.sun.jini.norm {
initialLookupGroups = new String[] { "your.group" };
}
(Note that there is no mention of Jeri in any of these files:
presumably it is a default - the JRMP version contains a
definition of
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.Remote;
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.export.*;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.activation.ActivationExporter;
public class RenewLease implements RemoteEventListener,
ProxyAccessor {
private Remote proxy;
public RenewLease(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);
}
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()));
}
public Object getProxy() {
return proxy;
}
}
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 (such
as the above
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
An Ant file to build, deploy and run the service is
In order to run the server, the following need to be running
reggie
to run as a lookup service
phoenix
to act as an activation server for the
norm
as a lease renewal service. Each lease will be
registered with this, and it will have the
!!! I HAVEN'T MADE ANY CHANGES BEYOND THIS POINT YET !!!
It is easy enough for a server to discover all of the lookup
services within reach at the time it is started, using
From Jini 1.1, there is a
phoenix
.
When there is a change to lookup services, the
The
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 has been modified in Jini
2.0 to be more flexible. It can be run in three modes, using either
Jeri (the default) or JRMP
transient
persistent
activatable (requires
To run
java \
-Djava.security.manager= \
-Djava.security.policy=fiddler-start-transient.policy \
-jar jini_install_dir/lib/start.jar \
config/fiddler-start-transient.config
where
The contents of
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.start.ServiceDescriptor;
com.sun.jini.start {
private static serviceCodebase =
new String("http://myHost:8080/fiddler-dl.jar");
private static servicePolicyFile =
new String("example_install_dir${/}policy${/}jeri-transient-fiddler.policy");
private static serviceClasspath =
new String("jini_install_dir${/}lib${/}fiddler.jar");
private static serviceImplName =
new String("com.sun.jini.fiddler.TransientFiddlerImpl");
private static serviceConfig =
new String("example_install_dir${/}config${/}jeri-transient-fiddler.config");
private static serviceArgsArray = new String[] { serviceConfig };
private static nonActivatableServiceDescriptor =
new NonActivatableServiceDescriptor(serviceCodebase,
servicePolicyFile,
serviceClasspath,
serviceImplName,
serviceArgsArray);
static serviceDescriptors =
new ServiceDescriptor[] { nonActivatableServiceDescriptor };
}//end com.sun.jini.start
The configuration file
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
com.sun.jini.fiddler {
private invocationLayerFactory = new BasicILFactory();
serverExporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
invocationLayerFactory,
false,
true);
initialLookupGroups = new String[] {"myGroup.myCompany.com"};
}//end com.sun.jini.fiddler
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
could be a little tricky as we attempt to find two different
services. We can simplify for this example by just doing a
sequential search using a
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
In order to run this example, you need to
Run a lookup service
Run an activation server
Run a lease renewal service
Run a lookup discovery service
Run the server. This will terminate, hopefully after finding
the services and registering the
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.
This work is licensed under a
Creative Commons License, the replacement for the earlier Open Content License.