Chapter 6: Service Registration

This chapter looks at how services register themelves with lookup locators so that they can later be found by clients.

6.1. ServiceRegistrar

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);
}

6.2. ServiceItem

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 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.

6.3. Registration

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

6.4. 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.

Figure 6.1: Objects in service registration

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

6.4.1 Running the 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 basic.SimpleService.xml:


	  
	

6.4.2 Information from the ServiceRegistration

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");

6.4.3 Service ID

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());

6.5. Entries

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, ...);

6.6. Summary

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.

6.7. Copyright

If you found this chapter of value, the full book "Foundations of Jini 2 Programming" is available from APress or Amazon .

This file is Copyright (©) 1999, 2000, 2001, 2003, 2004, 2005 by Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name.

Creative Commons License This work is licensed under a Creative Commons License, the replacement for the earlier Open Content License.