This chapter looks at how services register themelves with lookup locators so that they can later be found by clients.
A server for a service finds a service locator using unicast lookup
with a LookupLocator
or multicast search using
LookupDiscovery
. In both cases, a ServiceRegistrar
object is returned to act as a proxy for the lookup service.
The server then registers the service with the service locator using the
ServiceRegistrar
method register()
:
package net.jini.core.lookup;
public Class ServiceRegistrar {
public ServiceRegistration register(ServiceItem item,
long leaseDuration)
throws java.rmi.RemoteException;
}
The second parameter here is a request for the
length of time (in milliseconds) the lookup
service will keep the service registered.
This request need not be honored: the lookup service may reject it
completely, or only grant a lesser time interval. This is discussed
in the chapter on leases.
The first parameter is of type
package net.jini.core.lookup;
public Class ServiceItem {
public ServiceID serviceID;
public java.lang.Object service;
public Entry[] attributeSets;
public ServiceItem(ServiceID serviceID,
java.lang.Object service,
Entry[] attrSets);
}
The service provider will create a ServiceItem
object,
using the constructor
and pass it into register()
.
The serviceID
is set to null
when the service
is registered for the first time. The lookup service will set a non-null
value as it registers the service. On subsequent registrations or
re-registrations, this non-null value should be used. The
serviceID
is used as a globally unique identifier for the
service.
The second parameter is the service object that is being registered. This object will be serialised and sent to the service locator for storage. When a client later requests a service, this is the object it will be given. There are several things to note about the service object:
The object must be serialisable. Some objects, such as Swing's
JTextArea
are not serialisable at present and so
cannot be used.
The object is created in the service's JVM. However, when it runs it will do so in the client's JVM. It may need to be a proxy for the actual service. For example, it may be able to show a set of toaster controls, but will have to send messages across the network to the real toaster service, which is connected to the physical toaster.
If the service object is an RMI proxy, then the object in the
ServiceItem
is given by the programmer as the
UnicastRemoteObject
for the proxy stub, not the proxy
itself. The Java runtime substitutes the proxy. This subtlety
is explored in a later chapter.
The third parameter is a set of entries giving information about the
service in addition to the service object/service proxy itself. If there is no
additional information, this can be null
.
The service attempts to register itself by calling register()
.
This may throw an java.rmi.RemoteException
which must
be caught.
The second parameter is a request to the service locator for the length
of time to store the service. The time requested may or may not be honoured.
The return value is of type ServiceRegistration
This registration object is created by the lookup service and is returned
to run in the service provider. The registration acts as a proxy object to control the
state maintained about the exported service
object stored on the lookup service. Actually, this can be used to make changes
to the entire ServiceItem
stored on the lookup service.
The registration maintains a field serviceID
which is used
to identify the ServiceItem
on the lookup service.
This can be retrieved by getServiceID()
for reuse
by the server if it needs to do so (which it should).
These objects are shown in figure 6.1.
Other methods such as
void addAttributes(Entry[] attrSets);
void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets);
void setAttributes(Entry[] attrSets);
can be used to change the entry attributes stored on the lookup service.
The final public method for this class is getLease()
which returns a Lease
object, which allows renewal or
cancellation of the lease. This is discussed in more detail
in Leases
The major task of the server is then over. It will have successfully exported the service to a number of lookup services. What the server then does depends on how long it needs to keep the service alive or registered. If the exported service can do everything that the service needs to do, and does not need to maintain long-term registration, then the server can simply exit. More commonly, if the exported service object acts as a proxy and needs to communicate back to the service then the server can sleep so that it maintains existence of the service. If the service needs to be re-registered before timeout occurs then the server can also sleep in this situation.
A unicast server that exports its service and does nothing else is in the following program:
package basic;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import java.io.Serializable;
import java.rmi.RMISecurityManager;
/**
* SimpleService.java
*/
public class SimpleService implements Serializable {
static public void main(String argv[]) {
new SimpleService();
}
public SimpleService() {
LookupLocator lookup = null;
ServiceRegistrar registrar = null;
System.setSecurityManager(new RMISecurityManager());
try {
lookup = new LookupLocator("jini://localhost");
} catch(java.net.MalformedURLException e) {
System.err.println("Lookup failed: " + e.toString());
System.exit(1);
}
try {
registrar = lookup.getRegistrar();
} catch (java.io.IOException e) {
System.err.println("Registrar search failed: " + e.toString());
System.exit(1);
} catch (java.lang.ClassNotFoundException e) {
System.err.println("Registrar search failed: " + e.toString());
System.exit(1);
}
System.out.println("Found a registrar");
// register ourselves as service, with no serviceID
// or set of attributes
ServiceItem item = new ServiceItem(null, this, null);
ServiceRegistration reg = null;
try {
// ask to register for 10,000,000 milliseconds
reg = registrar.register(item, 10000000L);
} catch(java.rmi.RemoteException e) {
System.err.println("Register exception: " + e.toString());
}
System.out.println("Service registered with registration id: " +
reg.getServiceID());
// we can exit here if the exported service object can do
// everything, or we can sleep if it needs to communicate
// to us or we need to renew a lease later
//
// Typically, we will need to renew a lease later
}
} // SimpleService
The program needs to be compiled and run with
jsk-platform.jar
and jsk-lib.jar
in its CLASSPATH
.
In order to run it, a security policy must be specified as it uses
an RMIClassLoader
.
When run, it will attempt to connect to the service locator, so obviously one needs to be running on the machine specified in order for this to happen. Otherwise, it will throw an exception and terminate.
The instance data for the service object is transferred in serialized form across socket
connections. This instance data is kept in this serialized form by the lookup
services. Later, when a client asks for the service to be reconstituted, it will
use this instance data and also will need the class files.
At this point, the class files will also need to be transferred, probably by
an HTTP server.
There is no need for additional RMI support services such as
rmiregistry
or rmid
since all
registration is done by the method register()
.
An Ant file to build and run this is
The ServiceRegistrar
object is used to register()
the service, and in doing so returns a ServiceRegistration
object. This can be used to give information about the registration
itself. The relevant methods are
ServiceID getServiceID();
Lease getLease();
The service id can be stored by the application if it is going to
re-register again later.
The lease object can be used to control the lease granted by the
lookup locator, and will be discussed in more detail in the
chapter on Leases.
For now, we can just use it to find out how long the lease has been
granted for by its method getExpiration()
:
long duration = reg.getLease().getExpiration() -
System.currentTimeMillis();
System.out.println("Lease expires at: " +
duration +
" milliseconds from now");
A service is unique in the world. It runs on a particular machine and performs certain tasks. However, it will probably register itself with many lookup services. It should have the same "identity" on all of these. In addition if either the service or one of these locators crashes or restarts, then this identity should be the same as before.
The ServiceID
plays the role of unique identifier for a service.
It is a 128-bit number generated in a pseudo-random manner, and itshould be effectively
unique: the chance that a generator might duplicate this number should be
vanishingly small. There are two ways of getting a service ID: ask a lookup
service for one, or generate it yourself.
If the first argument to ServiceItem
is null, this is a request
to a lookup service to generate and return a service ID. This can then be used
as the first parameter to other service identifiers. This used to be the
preferred method, but if you register with multiple lookup services it can
lead to slightly messy logic.
The preferred method now seems to be for a service to generate the service id itself and give this to all the lookup services it foinds. This can be done by
import net.jini.id.Uuid;
import net.jini.id.Uuidfactory;
Uuid uuid = UuidFactory.generate();
ServiceID serviceID = new ServiceID(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits());
A service can announce a number of entry attributes when it registers
itself with a lookup service. It does so by preparing an array of
Entry
objects and passing them into the ServiceItem
used in the register()
method of the registrar.
The service can include as much as it wants to in this: in later searches
by clients each entry is treated as though it was or'ed
with the other entries. In other words, the more entries that are given
by the service,
the greater the chance of matching a client's requirements.
For example, we have a coffee machine on the 7th level of our building,
which is known as both "GP South Building" and "General Purpose
South Building". Information such as this, and general stuff about the
coffee machine can be encapsulated in the convenience classes
Location
and Comment
from the
net.jini.lookup.entry
package.
If this was on our network as a service, it would
advertise itself as
import net.jini.lookup.entry.Location;
import net.jini.lookup.entry.Comment;
Location loc1 = new Location("7", "728",
"GP South Building");
Location loc2 = new Location("7", "728",
"General Purpose South Building");
Comment comment = new Comment("DSTC coffee machine");
Entry[] entries = new Entry[] {loc1, loc2, comment};
ServiceItem item = new ServiceItem(..., ..., entries);
registrar.register(item, ...);
A service uses the ServiceRegistrar
object returned
as a proxy from a locator to register itself with that locator.
The service prepares a ServiceItem
that contains
a service object and a set of entries. The service object may
be a proxy for the real service. It registers this using the
register
method of the ServiceRegistrar
object.
Information about a registration is returned as a
ServiceRegistration
object. This may be queried
for information such as the lease and its duration.
If you found this chapter of value, the full book "Foundations of Jini 2 Programming" is available from APress or Amazon .
This work is licensed under a
Creative Commons License, the replacement for the earlier Open Content License.