Many of the examples in earlier chapters
use RMI proxies for services. These services subclass
UnicastRemoteObject
, and live within a server whose principal task
is to keep the service alive and registered with lookup services. If the server
fails to renew leases then lookup services will eventually discard it; if it
fails to keep itself and its service alive then the service will not be available
when a client wants to use it.
This results in a server and a service which most of the time will be idle,
probably swapped out to disk but still using virtual memory. In JDK 1.2,
the memory requirements on the server side can be enormous
(hopefully this will get fixed, but at
the moment this is a severe embarrassment to Java, and a potential threat to the
success of Jini). In JDK 1.2, there is an extension to RMI called
activation which allows an idle object to die, and be
recalled to life when needed. In this way, it does not occupy virtual memory
while idle. Of course, a process needs to be alive to restore such objects,
and RMI supplies a daemon rmid
to manage this. In effect,
rmid
acts as another virtual memory manager as it stores information
about dormant Java objects in its own files and restores them from there as
needed.
There is a serious limitation to rmid
: it is a Java program itself
and when running also uses enormous amounts of memory! So it only makes sense to
use this technique when you expect to be running a number of largely idle services
on the same machine. When a service is recalled to life,
or activated, a new JVM may be started to run the object.
This again increases memory use.
If memory use was the only concern, then there are a variety of other systems
such as echidna
which run multiple applications within a single
JVM. These may be adequate to solve memory issues.
However, RMI Activation is also designed to work with distributed objects,
and allows JVM's to hold remote references to objects which are no longer
active. Instead of throwing a remote exception on trying to access these
objects, the Activation system tries to resurrect the object using
rmid
to give a valid (and new) reference. Of course, if it
fails to do this, it will throw an exception anyway.
The principal place that this is used in the standard Jini distribution is with
the reggie
lookup service. reggie
is an activatable
service that starts, registers itself with rmid
and then exits.
Whenever lookup services are required, rmid
restarts reggie
in a new JVM. Clients of the lookup service are unaware of this mechanism:
they simply make calls on their proxy ServiceRegistration
object
and the Activation system looks after the rest. The main problem is for the
system administrator: getting reggie
to work in the first place!
The major concepts in Activation are the activatable object itself
(which extends java.rmi.activation.Activatable
) and
the environment in which it runs, an ActivationGroup
. A JVM
may have an activation group associated with it. If an object needs to be
activated and there is already a JVM running its group then it is restarted
within that JVM. Otherwise, a new JVM is started. An activation group may hold
a number of co-operating objects.
Making an object into an activable object requires subclassing from
Activatable
, and using a special two-argument constructor
which will be called when the object needs to be reconstructed.
There is a standard implementation of this constructor which just
passes up to the super class:
public ActivatableImpl(ActivationID id, MarshalledObject data)
throws RemoteException {
super(id, 0);
}
(The use of the marshalled data is discussed later).
This is the only change normally needed for a service. For example,
the file classifier becomes
package activation;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationID;
import java.rmi.MarshalledObject;
import common.MIMEType;
import common.FileClassifier;
import rmi.RemoteFileClassifier;
/**
* FileClassifierImpl.java
*/
public class FileClassifierImpl extends Activatable
implements RemoteFileClassifier {
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 id, MarshalledObject data)
throws java.rmi.RemoteException {
super(id, 0);
}
} // FileClassifierImpl
Note that an activatable object cannot have a ``normal'' constructor
to initialize itself, since this new constructor is how the object
is constructed by the activation system.
The server needs to create an activation group for the objects to run in.
The main issue in this is to set a security policy file. There are two
security policies in activatable objects: the policy used to
create the server
and export the service, and the policy used to run
the service.
The activation group sets a
policy file for running methods of the service object.
The policy file for the server is set
using the normal -Djava.security.policy=...
argument to starting
the server. After setting various parameters, the activation group is set for the JVM
by ActivationGroup.createGroup()
.
The ``standard'' remote objects that subclass UnicastRemoteObject
are created in the normal way using a constructor. Activatable objects are
not constructed in the server but are instead registered with rmid
,
which will look after construction on an as-needed basis. rmid
needs to know the class name and the location of the class files.
These are wrapped up in an ActivationDesc
, and registered with
rmid
by Activatable.register()
. This returns an RMI
stub object that can be registered with lookup services using the
ServiceRegistrar.register()
methods. This is also a little
different to subclasses of UnicastRemoteObject
which passes an
object which is converted to a stub by the RMI runtime. In point form,
UnicastRemoteObject
using its constructor
Activatable
is created by rmid
using a
special constructor
UnicastRemoteObject
object, the server needs to know the
class files for the class in its classpath,
the client needs to know the class files for the stub from an HTTP server
Activatable
object, rmid
needs to know the class
files from an HTTP server, the server must be able to find the stub files from
its classpath and the client must be able to get the stub files from an HTTP server
UnicastRemoteObject
object to the
ServiceRegistrar.register()
. This is converted to the stub by the
RMI runtime
Activatable
object from
Activatable.register()
. This stub is given directly to
ServiceRegistrar.register()
The changes for servers from the unicast remote objects are
rmid
The file classifier server which uses an activatable service is
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.lease.Lease;
import java.rmi.RMISecurityManager;
import java.rmi.MarshalledObject;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.util.Properties;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServer.java
*/
public class FileClassifierServer implements DiscoveryListener {
static final protected String SECURITY_POLICY_FILE =
"/home/jan/projects/jini/doc/policy.all";
// Don't forget the trailing '/'!
static final protected String CODEBASE = "http://localhost/classes/";
// protected FileClassifierImpl impl;
protected RemoteFileClassifier stub;
public static void main(String argv[]) {
new FileClassifierServer(argv);
// 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 FileClassifierServer(String[] argv) {
// install suitable security manager
System.setSecurityManager(new RMISecurityManager());
// Install an activation group
Properties props = new Properties();
props.put("java.security.policy",
SECURITY_POLICY_FILE);
ActivationGroupDesc.CommandEnvironment ace = null;
ActivationGroupDesc group = new ActivationGroupDesc(props, ace);
ActivationGroupID groupID = null;
try {
groupID = ActivationGroup.getSystem().registerGroup(group);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
try {
ActivationGroup.createGroup(groupID, group, 0);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
String codebase = CODEBASE;
MarshalledObject data = null;
ActivationDesc desc = null;
try {
desc = new ActivationDesc("activation.FileClassifierImpl",
codebase, data);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
System.out.println("Group ID " + ActivationGroup.currentGroupID().toString());
try {
stub = (RemoteFileClassifier) Activatable.register(desc);
} catch(UnknownGroupException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
}
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
RemoteFileClassifier service;
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
// export the proxy service
ServiceItem item = new ServiceItem(null,
stub,
null);
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.print("Register exception: ");
e.printStackTrace();
// System.exit(2);
continue;
}
try {
System.out.println("service registered at " +
registrar.getLocator().getHost());
} catch(Exception e) {
}
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServer
The service backend and the server must be compiled as usual, and in addition an
RMI stub object must be created for the service backend using rmic
(in JDK 1.2, at least).
The class files for the stub must be copied to somewhere that an HTTP server
can deliver them to clients. This is the same as for any other RMI stubs.
There is an extra step that must be performed for Activatable objects:
the activation server rmid
must be able to reconstruct a
copy of the service backend (the client must be able to
reconstruct a copy of the services's stub). This means that
rmid
must have access to the class files of the service backend, either from an
HTTP server or from the file system. In the example, the codebase
property in the ActivationDesc
is set to an HTTP url, so the
class files for the service backend must be accessible to an HTTP server.
Note that rmid
does not get the class files
for a service backend from the CLASSPATH
, but from the codebase
of the service.
The HTTP server need not be on the same machine as the service backend.
Before starting the service provider, an rmid
process must be set running
on the same
machine as the service provider. An HTTP server must be running
on a machine as specified by the codebase
property on the
service. The service provider can then be started. This will register the service with
rmid
, and will copy a stub object up to any lookup services that
are found. The server can then terminate (as mentioned earlier, this will
cause the service's lease to expire, but techniques to handle this are
described later).
In summary, there are typically three processes involved in getting an activatable service running
rmid
, which must be running on the same machine as the service
provider, and must be started before the service provider. It gets class
files using the codebase of the service
While the service remains registered with lookup services, clients can download
its RMI stub. The service will be created on demand by rmid
.
You only need to run the server once, since
rmid
keeps information about the service in it own log files.
Note that the JVM for the service will be created by rmid
,
and will be running in the same environment as rmid
. In particular,
such things as the current directory for the service will be
the same as for rmid
,
not from where the server ran. Similarly, the user id for the service will be the
user id of rmid
. This is a potential security problem in
multi-user systems. For example, any user on a Unix system could write a service
which attempts to read the shadow password file on the system, as an
activatable service. Once registered with rmid
, this same user
could write a client that calls the appropriate methods on the service.
If rmid
is running in privileged mode, owned by the superuser
of the system, then the service will run in that same mode and will happily
read any file in the entire file system! For safety, rmid
should probably be run using the user id nobody
, much like
the recommendations for HTTP servers.
An additional level of security can be added by specifying a security policy
file to rmid
by
rmid -J-Djava.security.policy="..."
I haven't got this to work properly yet :-(
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 rmid
.
If rmid
crashes and is restarted, or the machine is rebooted
and rmid
restarts, then it is able to use its log files to
restart any ``active'' services registered with it, as well as restore
``lazy'' services on demand. This can avoid messing around with boot
configuration files, by making services non-lazy and just ensuring that
rmid
is started on reboot.
An activatable object is created afresh each time a method is called
on it, using its two argument constructor. The default action, calling
super(id, 0)
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 MarshalledObject
instance. This is the same object
that was passed to the activation system in the ActivationDesc
parameter to Activation.register()
. 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 Activatable
doesn't have a no-args
constructor. So you can't subclass from Activatable
and have a constructor such as
FileClassifierMutable(String stateFile)
that doesn't
use the activation system. You can avoid this by not inheriting from
Activatable
, and register explicitly with the
activation system by e.g.
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 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 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 extends Activatable
implements RemoteFileClassifier {
/**
* Map of String extensions to MIME types
*/
protected Map map = new HashMap();
/**
* Permanent storage for the map while inactive
*/
protected String mapFile;
/**
* Listeners for change events
*/
protected 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 id, MarshalledObject data)
throws java.rmi.RemoteException {
super(id, 0);
try {
mapFile = (String) data.get();
} catch(Exception e) {
e.printStackTrace();
}
restoreMap();
}
} // 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 reggie
do).
package activation;
import mutable.RemoteFileClassifier;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import java.rmi.RMISecurityManager;
import java.rmi.MarshalledObject;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupDesc.CommandEnvironment;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationGroup;
import java.rmi.activation.ActivationGroupID;
import java.util.Properties;
import java.rmi.activation.UnknownGroupException;
import java.rmi.activation.ActivationException;
import java.rmi.RemoteException;
/**
* FileClassifierServerMutable.java
*/
public class FileClassifierServerMutable 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/";
static final protected String LOG_FILE = "/tmp/file_classifier";
// protected FileClassifierImpl impl;
protected RemoteFileClassifier stub;
public static void main(String argv[]) {
new FileClassifierServerMutable(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 FileClassifierServerMutable(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;
try {
data = new MarshalledObject(LOG_FILE);
} catch(java.io.IOException e) {
e.printStackTrace();
}
ActivationDesc desc = null;
try {
desc = new ActivationDesc("activation.FileClassifierMutable",
codebase, data);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
}
System.out.println("Group ID " + ActivationGroup.currentGroupID().toString());
try {
stub = (RemoteFileClassifier) Activatable.register(desc);
} catch(UnknownGroupException e) {
e.printStackTrace();
System.exit(1);
} catch(ActivationException e) {
e.printStackTrace();
System.exit(1);
} catch(RemoteException e) {
e.printStackTrace();
System.exit(1);
}
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
RemoteFileClassifier service;
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
// export the proxy service
ServiceItem item = new ServiceItem(null,
stub,
null);
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.print("Register exception: ");
e.printStackTrace();
// System.exit(2);
continue;
}
try {
System.out.println("service registered at " +
registrar.getLocator().getHost());
} catch(Exception e) {
}
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServerMutable
This example used a simple way to store state. Sun uses a far more complex system
in many of its services such as reggie
. This is a
``reliable log'', in package 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.
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 ServiceRegistrar
such as reggie
will generate an event when a lease changes status,
but this is a ``service removed'' event rather than a ``service about to be removed''
event - it is too late.
If a server is alive, then it can use a
LeaseRenewalManager
to keep leases alive, but firstly the
renewal manager
works by sleeping and waking up just in time to renew the leases, and secondly,
if the server exits then no LeaseRenewalManager
will continue
to run.
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 LeaseRenewalManager
in keeping track of leases registered
with it, and renewing them as needed. In general, it can keep leases alive
without waking the service itself, which can slumber till activated by
clients calling methods.
There is a small hiccup in this: how long should the
LeaseRenewalService
keep renewing leases for a service?
The LeaseRenewalManager
utility has a simple solution: keep
renewing while the server for that service is alive. If the server dies,
taking down a service, then it will also take down the
LeaseRenewalManager
running in the same JVM,
so leases will expire as expected after
an interval.
But this mechanism won't work
for LeaseRenewalService
because the managed service can
disappear without the LeaseRenewalService
knowing about it.
So the lease renewal must be done on a leased basis itself! The
LeaseRenewalService
will renew leases for a service only
for a particular amount of time, specified by a lease. The service will
still have to renew its lease, even though it is with a
LeaseRenewalService
instead of a bunch of lookup services!
The lease granted by this service should be of much longer duration than
those granted by the lookup services for this to be of value.
Activatable services can only be woken by calling one of their methods.
The LeaseRenewalService
accomplishes this by generating
renewal events in advance
and calling a 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 rmid
process managing
the service has died or is unavailable, then the event will not be delivered
and the LeaseRenewalService
can remove this service from its
renewal list.
This is not quite satisfactory for other types of ``dormant'' services such
as might exist on mobile phones, since there is no equivalent of
rmid
to handle activation. Instead, the mobile phone service
might say that it will connect once a day and renew the lease, as long
as the LeaseRenewalService
agrees to keep the lease for at least a day.
This is still ``negotiable'', in that the service asks for a duration and the
LeaseRenewalService
replies with a value that might not be so
long. Still, it should be better than dealing with the lookup services.
Jini 1.1 supplies an implementation of LeaseRenewalService
called norm
. This is a non-lazy Activatable service,
which requires rmid
to be running.
This is run by
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
service. This exports RMI stubs and the class definitions for
these are in norm-dl.jar
. The second security file defines the
policy file that will be used in execution of the
LeaseRenewalService
methods. Finally, the log file is used to
keep state, so that it can keep track of the leases it is managing.
The norm
service will maintain a set of leases for a period
of upto 2 hours. The reggie
lookup service only grants
leases for 5 minutes, so that using this service increases the amount
of time between renewing leases by a factor of over twenty.
The norm
service exports an object of type
LeaseRenewalService
which is defined by the interface
package net.jini.lease;
public interface LeaseRenewalService {
LeaseRenewalSet createLeaseRenewalSet(long leaseDuration)
throws java.rmi.RemoteException;
}
The leaseDuration
is a requested value in milliseconds for
the lease service to manage a set of leases. The lease service creates a
lease for this request, and in order for it to continue to manage the
set beyond the lease's expiry, the lease must be renewed before expiration.
Since the service may be inactive around the time of expiry, the
LeaseRenewalSet
can be asked to register a listener object
that will receive an event containing the lease. This will activate a
dormant listener so that it can renew the lease in time.
If the lease for the LeaseRenewalSet
is allowed to lapse,
then eventually all the leases for the services it was managing will also
expire, making the services unavailable.
The LeaseRenewalSet
returned from 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 LeaseRenewalSet
will keep renewing the lease
until either the requested membershipDuration
expires or the
lease on the whole LeaseRenewalSet
expires (or an exception
happens, like a lease being refused).
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 ExpirationWarningEvent
package net.jini.lease;
public class ExpirationWarningEvent extends RemoteEvent {
Lease getLease();
}
This allows the listener to get the lease for the LeaseRenewalSet
,
and (probably) renew it. A simple activatable class that can renew the lease is
/**
* @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
LeaseRenewalService
from a lookup service
LeaseRenewalService
. Since it may find lookup services before it
finds the renewal service, it will need to keep a list of lookup services found
before finding the service, in order to register them with it
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 LookupDiscovery
.
While the server continues to stay alive, any new lookup services that
start will also be found by LookupDiscovery
. But if the
server terminates, which it will for activable services, then these
extra lookup services will probably never be found. This will result
in the service not being registered with them, which could mean in turn that
clients may not find it. This is analogous to leases not being renewed if the
server terminates.
Jini 1.1 supplies a service, the LookupDiscoveryService
, that
can be used to continuously monitor the state of lookup services. It will
monitor these on behalf of a service that will most likely want to register
with each new lookup service as it starts. If the service is an activatable one,
the the server that would have done this will have terminated, as its role
would have just been to register the service with rmid
.
When there is a change to lookup services, the LookupDiscoveryService
need to notify an object about this, by sending it a remote event
(actually of type RemoteDiscoveryEvent
). But again, we do not
want to have a process sitting around waiting for such notification, so the
listener object will probably also be an activatable object.
The LookupDiscoveryService
interface has specification
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 RemoteDiscoveryEvent
events
and call the 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
LookupDiscoveryRegistration
object returned 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 reggie
. It is also a non-lazy
activatable service.
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 RenewLease
object to do this, as in the
last section.
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 DiscoveryChange
object with method 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
DiscoveryChange
object must know both the service to be registered, and the lease
renewal service. This is static data, so these two objects can be passed in an array of
two objects as the MarshalledObject
to the activation constructor.
The class itself can be implemented as
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 ClientLookupManager
to find the lease service, and register
our lease renewal object with it. Now that it has a proxy for the service object,
and also a lease renewal service, it can create the marshalled data for the
lookup discovery service and register this with rmid
.
Now we can find the lookup discovery service, and register our discovery
change listener DiscoveryChange
with it. At the same time,
we have to register the service with all the lookup services the lookup
discovery service finds on initialisation. This all leads to
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