Chapter 7: Client Search

This chapter looks at what the client has to do once it has found a service locator and wishes to find a service.

7.1. ServiceRegistrar

A client gets a ServiceRegistrar object from the lookup service. It uses this to search for a service stored on that lookup service using the lookup() method:


public Class ServiceRegistrar {
    public java.lang.Object lookup(ServiceTemplate tmpl)
                            throws java.rmi.RemoteException;
    public ServiceMatches lookup(ServiceTemplate tmpl,
                                 int maxMatches)
                            throws java.rmi.RemoteException;
}
The first of these methods just finds a service that matches the request. The second finds a set (upto the maxMatches) requested.

The lookup methods use a class of type ServiceTemplate to specify the service looked for:


package net.jini.core.lookup;

public Class ServiceTemplate {
    public ServiceID serviceID;
    public java.lang.Class[] serviceTypes;
    public Entry[] attributeSetTemplates;

    ServiceTemplate(ServiceID serviceID, 
                    java.lang.Class[] serviceTypes, 
                    Entry[] attrSetTemplates);
}
Although each service should have been assigned a serviceID by a lookup service, a client might not know this (it could be the first time the client has looked for this service, for example). The serviceID is set to null in this case. If the client does know the serviceID then it can set the value to find the service. The attributeSetTemplates is a set of Entry elements to perform matching of attributes, as discussed later in this chapter.

The major parameter to the lookup() method is a list of serviceTypes. We know that services export instances of a class. How does the client ask so that it gets a suitable instance delivered from the lookup locator? Although the lookup services keep instances of objects for the service, the client will only know about a service from its specification (unless it already has a serviceID for the service). The specification will almost certainly be a Java interface, so the client needs to ask using this interface. An interface can have a class object in the way as ordinary classes, so the list of serviceTypes will typically be a list of class objects for service interfaces. So the client will usually request a interface object.

To be more concrete, a toaster may be defined by an interface


public interface Toaster extends java.io.Serializable {
    public void setDarkness(int dark);
    public void startToasting();
}
A Breville "Extra Lift" toaster will implement this in one particular way, as will other toasters

public class BrevilleExtraLiftToaster implements Toaster {
    public void setDarkness(int dark) {
        ...
    }
    public void startToasting() {
        ...
    }
}
When the toaster service starts, it exports an object of class BrevilleExtraLiftToaster to the lookup service. However, the client does not know what type of toaster is out there, so will make a request such as

    System.setSecurityManager(new RMISecurityManager());

    // specify the interface object
    Class[] toasterClasses = new Class[1];
    toasterClasses[0] = Toaster.class;

    // prepare a search template of serviceID, classes and entries
    ServiceTemplate template = new ServiceTemplate(null, 
                                                   toasterClasses, 
                                                   null);

    // now find a toaster
    Toaster toaster = null;
    try {
        toaster = (Toaster) registrar.lookup(template);
    } catch(java.rmi.RemoteException e) {
        System.exit(2);
    }
Notice that the lookup() can throw an exception. This can occur, if say, the service requested cannot be de-serialised.

At the end of this, an object has been transported across to the client that is an instance of a class implementing the Toaster interface, and has been coerced to be of this type. This object has two methods setDarkness() and startToasting(). No other information is available about the toaster capabilities, because the interface does not specify any more and in this case the set of attribute values was null. So the client can call


    toaster.setDarkness(1);
    toaster.startToasting();

Before leaving this section, what is the role of System.setSecurityManager(new RMISecurityManager())? A serialized object has been transported acrosss the network, and is reconstituted and coerced to an object implementing Toaster. We know that here it will in fact be an object of class BrevilleExtraLiftToaster, but the client doesn't need to know that. Or does it? Certainly the client will not have a class definition for this class on its side. But when the toaster object begins to run, then it must run using its BrevilleExtraLiftToaster code! Where does it get it from? From the server, most likely by an HTTP request on the server. This means that it is loading a class definition across the network, and this requires security access. So a security manager capable of granting this access must be installed before the load request is made.

Note the difference between loading a serialized instance and loading a class definition: the first does not require access rights, only the second one does. So if the client had the class definitions of all possible toasters then it would never need to load a class. In this case, it would not need a security manager that allows classes to be loaded across the network. This is not likely, but may perhaps be needed in a high security environment.

7.2. ServiceMatches

If a client wishes to search for more than one match to a service request from a particular lookup service, then it specifies the maximum number of matches it would like returned by the maxMatches parameter of the second for lookup() method. It gets back a ServiceMatches object.


package net.jini.core.lookup;

public Class ServiceMatches {
    public ServiceItem[] items;
    public int totalMatches ;
}
The number of elements in items need not be the same as totalMatches! Suppose there are five matching services stored on the locator. Then totalMatches will be set to five after a lookup. However, if you only specified to search for at most two matches, then items will be set to be an array with only two elements.

In addition, not all elements of this array need be non-null! Note that in lookup(tmpl) when asking for only one match, an exception can be returned such as when the service is not serialisable. No exception is thrown here, because although one match might be bad, the others may still be okay. So a value of null as the array element value is used to signify this


    ServiceMatches matches = registrar.lookup(template, 10);
    // NB: matches.totalMatches may be greater than matches.items.length
    for (int n = 0; n < matches.items.length; n++) {
        Toaster toaster = (Toaster) matches.items[n].service;
	if (toaster != null) {
	    toaster.setDarkness(1);
	    toaster.startToasting();
        }
    }
(which will start upto 10 toasters cooking at once!)

7.3. Matching

A client is attempting to find one or more services that satisfy its requirements. It does so by creating a ServiceTemplate object and using this in a registrar's lookup() call. A ServiceTemplate object has three fields


ServiceID         serviceID;
java.lang.Class[] serviceTypes;
Entry[]           attributeSetTemplates;
An overview: firstly, if the client is repeating a request, then it may have recorded the serviceID from an earlier request. The serviceID is a "universally unique identifier" so can be used to identify a service unambiguously. This can be used by the service locator as a filter to quickly discard other services. Secondly, a client may want to find a service satisfying a number of interface requirements at once. For example, a client may look for a service that implements both Toaster and FireAlarm (so that it can properly handle burnt toast). Finally, the client will specify a set of attributes that must be satisfied by each service. Each attribute required by the client is taken in turn and matched against the set offered by the service. For example, in addition to requesting a Toaster with a FireAlarm, a client entry may specify a location in "GP South Building".This will be tried against all the variations of location offered by the service. A single match is good enough. An additional client requirement of, say, manufacturer would also have to be matched by the service.

More formally from the ServiceTemplate API documentation:

  1. A service item (item) matches a service template (tmpl) if: item.serviceID equals tmpl.serviceID (or if tmpl.serviceID is null); and item.service is an instance of every type in tmpl.serviceTypes; and item.attributeSets contains at least one matching entry for each entry template in tmpl.attributeSetTemplates.

  2. An entry matches an entry template if the class of the template is the same as, or a superclass of, the class of the entry, and every non-null field in the template equals the corresponding field of the entry. Every entry can be used to match more than one template. Note that in a service template, for serviceTypes and attributeSetTemplates, a null field is equivalent to an empty array; both represent a wildcard.

7.4. Summary

A client prepares a ServiceTemplate which is a list of class objects and a list of entries. For each service locator that is found, the client can query this using the ServiceRegistrar object's lookup() method, to see if the locator has a service matching the template. If the match is successful, an object is returned that can be cast into the class required. Service methods can then be invoked on this object.

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