Finding Services with the Jini Lookup Service

The Power and Limitations of the ServiceRegistrar Interface

by Bill Venners
February 15, 2000

First published in JavaWorld, February 2000
Summary
In this article, I look at ways Jini clients can search for desired services in the Jini lookup service. I describe both the flexibility of the lookup service as well as its inherent limitations. I proposes a way that the ServiceRegistrar interface could evolve, and describes the Jini team's response.

The Jini lookup service, the central component of Jini's runtime infrastructure, offers Jini clients a flexible and powerful way to find Jini services. It enables service providers to advertise their services and enables clients to locate and enlist the help of those services.

To interact with the lookup service, the client must first obtain a a service registrar object via discovery, a network-level protocol used by Jini's runtime infrastructure. Discovery enables clients and services to locate lookup services. (For more information on discovery, see Resources.) The service registrar object, which implements the net.jini.core.lookup.ServiceRegistrar interface, enables the client to interact with the lookup service. To find desired services, clients build a ServiceTemplate, an instance of class net.jini.core.lookup.ServiceTemplate, and pass it to one of two lookup() methods declared in the ServiceRegistrar interface. Each lookup() method sends the service template to the lookup service, which performs the query and returns zero to many matching service objects to the client.

In general, a client looks up a service by Java type, usually an interface. For example, if a client needed to use a printer, it would compose a service template that included a Class object for a well-known interface to printer services. All printer services would implement this well-known interface. The lookup service would return a service object (or objects) that implemented this interface. You could include attributes in the service template to narrow the number of matches for such a type-based search. The client would use the printer service by invoking on the service object methods declared in the well-known interface.

The ServiceTemplate class

The ServiceTemplate class, which allows you to express search criteria for Jini lookups, consists solely of these three public fields:

public Entry[] attributeSetTemplates;
public ServiceID serviceID;
public Class[] serviceTypes;

ServiceTemplate has no methods and its instances merely serve as struct-like containers for lookup service queries. Matches are performed as described by the following excerpt from ServiceTemplate's javadoc page:

Items in the lookup service are matched using an instance of [ ServiceTemplate]. A service item ( item) matches a service template ( tmpl) if:
  • item.serviceID equals tmpl.serviceID (or if tmpl.serviceID is null)
  • item.service [the service object] is an instance of every type in tmpl.serviceTypes
  • item.attributeSets contains at least one matching entry for each entry template in tmpl.attributeSetTemplates

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.

As described here, the service template can include a reference to an array of Class objects. These Class objects indicate to the lookup service the Java type (or types) of the service object the client desires. The service template can also include a service ID, which uniquely identifies a service, and attributes, which must exactly match the attributes uploaded by the service provider in the service item. The service template can also contain wildcards for any of these fields. A wildcard in the service ID field, for example, will match any service ID.

The lookup() methods

The ServiceRegistrar's lookup() methods take two overloaded forms. The main difference between these two forms is the number of matches and service items each one returns. The two-parameter form can return multiple matches of the query expressed in the ServiceTemplate, while the one-parameter form returns only one match. In addition, the two-parameter form returns entire service items; the one-parameter form returns only the service object.

The two-parameter form of lookup()

Here's a javadoc excerpt that explains the two-parameter form of lookup():

public ServiceMatches lookup(ServiceTemplate tmpl,
    int maxMatches) throws java.rmi.RemoteException;
Returns at most maxMatches items matching the template, plus the total number of items that match the template. The return value is never null, and the returned items array is only null if maxMatches is zero. For each returned item, if the service object cannot be deserialized, the service field of the item is set to null and no exception is thrown. Similarly, if an attribute set cannot be deserialized, that element of the attributeSets array is set to null and no exception is thrown.

Here is the ServiceMatches class:

package net.jini.core.lookup;

public class ServiceMatches extends java.lang.Object
    implements java.io.Serializable {

    public ServiceItem[] items;
    public int totalMatches;
}

And here is the ServiceItem class:

package net.jini.core.lookup;

public class ServiceMatches extends java.lang.Object
    implements java.io.Serializable {

    public Entry[] attributeSets;
    public java.lang.Object service;
    public ServiceID serviceID;
}

As mentioned previously, each element of the items array returned by the two-parameter form is a complete service item, which includes the service object, service ID, and all the attribute sets. The maxMatches field helps clients manage the number of objects returned by this lookup().

The length of the items array in the returned ServiceMatches object will be less than or equal to the value passed to lookup() in maxMatches. The total number of matching service items, which is returned in totalMatches, will be greater than or equal to the length of the items array.

For example, if maxMatches is 50 and the service template matches 25 items, the length of the returned items array and the value of totalMatches will both be 25. Alternatively, if maxMatches is 50 but the service template matches 100 items, the length of the returned items array will be 50 and the value of totalMatches will be 100. When a service template matches more than maxMatches service items, the service items returned by the two-parameter lookup() are selected randomly from the full set of matching service items.

The one-parameter form of lookup()

The one-parameter lookup() method returns one matching service object chosen randomly from all the matches. Here's a javadoc excerpt explaining this form:

public ServiceMatches lookup(ServiceTemplate tmpl)
    throws java.rmi.RemoteException;
Returns the service object (i.e., just ServiceItem.service) from an item matching the template, or null if there is no match. If multiple items match the template, it is arbitrary as to which service object is returned. If the returned object cannot be deserialized, an UnmarshalException is thrown with the standard RMI semantics.

Because the one-parameter lookup() returns only one matching service object, clients can minimize the amount of object state and class files that are downloaded. But because the returned service object is selected arbitrarily, and is not identified by a service ID or described by associated attribute sets, the client must be confident that any matching service object will suffice.

The browsing methods

In addition to the two lookup() methods, the ServiceRegistrar has three methods called browsing methods that let clients get information about registered service items. These three methods -- getServiceTypes(), getEntryClasses(), and getFieldValues() -- are called "browsing methods" because they enable clients to browse the services and attributes in the lookup service.

The getServiceTypes() method takes a ServiceTemplate (the same ServiceTemplate that is passed to the lookup() methods) and a String prefix. It returns an array of Class instances representing the most specific types (classes or interfaces) of the service objects that match the template. These service objects are neither equal to, nor a superclass of, any of the types specified in the template and have names that start with the specified prefix. The service object or objects for whom Class instances are returned are all instances of all the types (if any) passed in the template, but the Class instances are all more specific than (and are subclasses or subinterfaces of) those types. Each class appears only once in the returned array, and in arbitrary order.

Here's what getServiceTypes() looks like:

public java.lang.Class[] getServiceTypes(ServiceTemplate tmpl,
    java.lang.String prefix) throws java.rmi.RemoteException;

The getEntryTypes() method takes a ServiceTemplate, and returns an array of Class instances that represent the most specific classes of entries for the service items that match the template which either don't match any entry template or are a subclass of one. Each class appears only once in the returned array, and again in arbitrary order.

Here's what getEntryClasses() looks like:

public java.lang.Class[] getEntryClasses(ServiceTemplate tmpl)
    throws java.rmi.RemoteException;
The getFieldValues() method takes a ServiceTemplate, an integer index, and a String field name. It returns an array of Objects for the named field of all instances of the entry that appears in the ServiceTemplate's Entry[] array at any matching service item's passed index. Each object of a particular class and value appears only once in the returned array, and in arbitrary order.

Here's what getFieldValues() looks like:

public java.lang.Object[] getFieldValues(ServiceTemplate tmpl,
    int setIndex, java.lang.String field)
    throws java.lang.NoSuchFieldException, java.rmi.RemoteException;

The behavior and purpose of these browsing methods can be obscure. A good way to think of them is that they enable clients to incrementally narrow queries of the lookup service.

For example, a client, such as a graphical lookup service browser, could first invoke getServiceTypes() with an empty template. The getServiceTemplate() method returns all possible service types registered in the lookup service, which the browser could display. The user could select one or more types, then push the Requery button. The browser would add that type (or types) to the service template, and invoke getServiceTypes() again. A smaller list of types would be returned, and displayed by the browser. The user could select one and press an Entries button. The browser would form a template with the most recently selected service type or types, and invoke getEntryTypes(). The getEntryTypes() method would return an array of entry classes, which the browser could then display. The user could select some entries, and a field of a selected entry, and push a Fields button. The browser would build a template using the currently selected service and entry types, and pass the index of the entry class in which the user selected the field, and the name of the selected field, to getFieldValues(). The browser would display all the values that getFieldValues() returned. The user could use these values to further narrow the search for a service, eventually using a particular service. Thus, these methods help clients, whether or not a human user is involved, to browse the services registered in a lookup service. The arrays returned from the browsing methods can help the client further refine its queries, ultimately resulting in a ServiceTemplate that, when passed to lookup(), returns the most appropriate service object.

The notify() method

In addition to the lookup and browsing methods, the ServiceRegistrar interface also includes a notify() method that notifies clients when new services register or unregister with a lookup service:

public EventRegistration notify(ServiceTemplate tmpl,
    int transitions, RemoteEventListener listener,
    MarshalledObject handback, long leaseDuration)
    throws RemoteException;

You invoke notify() to register yourself (or another listener) to receive a distributed event whenever the services that match the passed ServiceTemplate undergo a state change described by the transitions parameter.

The transitions parameter is a bitwise OR of any non-empty set of these three values, which are defined as constants in ServiceRegistrar:

TRANSITION_MATCH_MATCH
TRANSITION_MATCH_NOMATCH
TRANSITION_NOMATCH_MATCH

You build the ServiceTemplate for notify() in the same way you build it for lookup(). You can indicate explicit types, a service ID, attributes (which must exactly match), or wildcards (which match anything) in any of those fields. The transitions are based on a change (or non-change) in the status of whatever matches your ServiceTemplate before and after any operation is performed on the lookup service.

For example, TRANSITION_MATCH_MATCH indicates that at least one service item matched your template before and after an operation. TRANSITION_MATCH_NOMATCH indicates that, although at least one particular service item matched your template before an operation, it no longer matched your template after the operation. To receive notification when any new services are added to a lookup service, you simply specify a template that matches any service, and pass TRANSITION_NOMATCH_MATCH as the transition to the notify() method.

Lookup servers versus name servers

To fully appreciate the flexibility and power of Jini's lookup service, it's useful to compare it to name servers such as the RMI registry. The RMI registry is similar to the Jini lookup service in that objects can be registered in either server, and clients can query both for objects. When a client queries either server for an object, the server sends the object across the network to the client.

The RMI registry is a name server, because each RMI remote object is associated with a character string name that is unique within that registry. To perform a query on an RMI registry, clients must specify that name. In return for the name, clients receive a stub object that serves as a remote reference to the remote object in the queried RMI registry.

Jini provides a much more expressive way to search for services than RMI. Jini services are registered with a service ID that is globally unique and permanently maintained by each service, a service object, and any number of attribute objects. Using the ServiceTemplate, clients can look up a service by service ID, by the Java type or types of the service object, and by wildcard and exact matching of the attributes. Whereas RMI requires that a client know the registered name of a desired remote object, Jini enables a client to just look for the type of service desired -- therefore enabling spontaneous networking. If you enter a LAN environment for the first time and you want to use a printer, you don't have to figure out the printer service's registered name; instead, you just look up the services that implement a well-known printer interface. Lookup by type also ensures that Jini clients will know how to use whatever is returned from their query, because they had to have knowledge of the type before sending their query.

On the other hand, the Jini lookup service does operate similar to a naming server in two ways. First, performing lookups by service ID is really looking up services by name. Just as the character string name of a remote object in RMI registry is unique, a service ID is a unique name for a Jini service. A Jini service ID differs from an RMI registry name only in its form and its scope of uniqueness. Whereas an RMI registry name is a character string, a Jini service ID is a 128-bit number. And whereas an RMI registry name is guaranteed to be unique only within the scope of each particular RMI registry server, a Jini service ID is supposed to be globally unique.

The other way in which a Jini lookup service operates as a naming server is that lookups by type are actually implemented by performing string comparisons of type names. As mentioned previously, to specify the kind of service desired, clients specify Java types. The types specified are most often interfaces, but can also be classes. Because developers of Jini clients must indicate the desired service with a Java type, he or she knows about the type at compile-time, and will therefore know how to use whatever object is returned.

To indicate a Java type, clients must pass to the service registrar's lookup() method an array of references to Class instances that represent the desired types in the client's virtual machine. Any registered service whose service object is an instance of each type specified by the client matches the lookup. When the lookup service looks through its set of registered services, however, it merely compares the type names of the types appearing in the client's Class[] array with the type names of the registered service objects. The lookup service doesn't compare any class information besides the type name.

Because the lookup service compares types by name only, two different types with the same name would match. Nevertheless, were the service object for such a mismatched type to arrive at the client, the deserialization process on the client side would detect the mismatch and throw an exception.

The reason the lookup service can get by with performing merely a string comparison of the type names, and ignoring all other class information, is that:

  1. Java objects come with a global naming scheme that's supposed to make all fully qualified names unique. IBM, for example, is responsible for making sure no two types in the com.ibm namespace have the same name, and it is not supposed to let the world see anything it made whose name doesn't start with "com.ibm" (or the reverse form of any other Internet domain name it controls).
  2. Java has a set of rules of binary compatibility that enable each named type to evolve without breaking pre-existing code that uses the earlier versions of the named type.
  3. Java type names are intimately associated with an interface and a semantic contract that instances of that named type are supposed to fulfill. So a Java type name has more meaning than an arbitrarily assigned logical name.

The last item in the previous list is one of the most important differences between a traditional name server, such as RMI Registry, and the Jini lookup service. A Java type name is associated with an interface and a semantic contract that gives meaning to the name. That meaning should be the same worldwide because of the first item in the list, the recommended naming convention. The name assigned to a remote object registered in an RMI Registry, by contrast, is an arbitrarily chosen name that by itself says nothing about the services offered by the referenced remote object.

One final difference between a traditional name server and the Jini lookup server is that Jini service objects can in effect have multiple names. Objects can have many names because they can be instances of many different types. Thus an object that has two type names, "Printer" and "Copier" can be located by two different clients looking for either a "Printer" or "Copier" object.

The limitations of exact matching

Although ServiceTemplate gives clients a very powerful way to express search criteria, it has some limits. The fields of attribute sets in the ServiceTemplate's attributeSetTemplates array must match exactly the corresponding fields in the attribute sets of service items, unless the template field is null. Null template fields are wildcards, which match anything. The limit inherent in this forced choice between exact matching and wildcard matching is that many conceivable search criteria, such as criteria that include ranges of values to match or sets of values to match, cannot be expressed with a single ServiceTemplate.

Some perspective on the extent to which ServiceTemplate can express search criteria can be found in the following paragraph from the Jini Lookup Attribute Schema Specification:

No matter how much information it has at its disposal, a client of the lookup service will not always be able to find a single unique match without assistance when it performs a lookup. In many instances we expect that more than one service will match a particular query. Accordingly, both the lookup service and the attribute schema are geared toward reducing the number of matches that are returned on a given lookup to a minimum, and not necessarily to just one.

At the most recent Jini Community Summit, which took place in October 1999 in Annapolis, Alan Kaminsky of Xerox gave a presentation in which he mentioned that the Printer Working Group encountered the limitations of ServiceTemplate and ServiceRegistrar. Kaminsky said that although the Printer Working Group considered proposing changes to ServiceRegistrar to obtain richer lookup capabilities, it settled on proposing helper services to provide those capabilities.

One reason the printer group decided to propose a helper service is that it wanted to include predicates in the search criteria, such as:

field alpha is greater 12 AND field beta is less than or equal to 98

Unlike any of the existing lookup or browsing methods of ServiceRegistrar, sending a predicate could require the lookup server to execute client code. Ultimately, the goal of the ServiceRegistrar interface is to provide a base set of lookup and browse capabilities to clients of Jini lookup services. More elaborate search capabilities could be implemented directly by clients or provided by helper services.

ServiceRegistrar and the Jini community

One somewhat unusual aspect of Jini technology is that Sun has distributed the Jini source code under the Sun Community Source License (SCSL) and encouraged the formation of a community around the code. If you read the license agreement, you will likely get the idea that SCSL is not the typical open source license agreement, such as the GNU Public License, because the license basically states that Sun owns whatever the community contributes to the code. Ken Arnold made a reasonable case at the Annapolis Jini Community Summit. He said that the SCSL looked like it did because Sun was trying to make sure no one was able to modify the code base and distribute incompatible versions of it. (Although Ken mentioned no names explicitly, it was apparent to me that Sun was primarily worried about a company that Allen Holub often describes as: "A company that will remain unnamed that starts with 'M.'") The realities of SCSL are worth investigating, but I'll save that for a future article.

For now, I'd like to stimulate some thought and discussion about the ways in which the ServiceRegistrar interface could be modified in the future. The reason I feel this is justifiable is simply that the Jini code, including the ServiceRegistrar interface, is to some extent open source, and therefore the Jini community has a say in how the code should evolve. I'm going to describe what I came to consider missing functionality in the ServiceRegistrar while thinking about how to add a UI to a service. I'll describe my proposal for filling in this missing functionality, and the response from the Jini team at Sun.

Handling low-resource clients

In the early days of the ServiceUI community project, Bob Scheifler of Sun, in response to a proposal in which clients invoked IU factory methods (methods that produce UIs for a Jini service) declared in the service object, posted these wise words:

Because the JLS allows wide variation in when resolution occurs in a JVM, it seems this style [of placing UI factory methods in the service object] can result in eager download of all of the UI classes, even if none of them are actually ever used. It may be desirable to find a design that can avoid this, if possible.

In response to a later proposal in which the UI factory methods were moved to attribute sets, Scheifler pointed out that eager downloading was still possible:

Deserializing the actual factory instance may also cause significant class loading and downloading. Compound this if the service registers with multiple UI entries. And all of this happens even if the client doesn't use any of the UIs.

You may well respond that this is a general design issue with the lookup service: you can get all of the attributes or none, but you can't selectively choose, and you can't delay unmarshalling of them. I would agree this is an interesting area to mull over. But in the meantime, I think the UI design needs to be targeted at how things work today, and the footprint impact of attributes on clients needs to be considered with some care.

Bob Scheifler's and other posted comments got me thinking that perhaps the ServiceUI proposal should provide a way for low-resource clients to find a UI for a given service without requiring that the client download the entire service item. As I describe later in this article, a more direct way to deal with the eager class loading problem is via an appropriate scheme to keep UI and service codebases separate. Nevertheless, I felt it made sense that some clients might not be able to deserialize all the objects contained in a service item because, after all, Jini is targeted at embedded devices, which often have limited memory.

When I went to address this concern for low-resource clients, I was surprised to discover that the ServiceRegistrar interface offered few options for low-resource clients. For a while, I dabbled with an approach that relied on getFieldValues() to grab a UI factory object out of an Entry. But in the end, I realized that getFieldValues() couldn't really help, because there's no limit to the number of items it will return.

A low-resource client could narrow its search by using getServiceTypes(), getEntryClasses(), or by just picking a single Entry class. But because getEntryClasses() removes duplicates, the low-resource client has no way of knowing how many entries of that type exist. It could be one, or a hundred, or a million. Thus, when the client invokes getFieldValues(), it might get a million objects back.

Ultimately, I concluded that the best way to address the low-resource client requirements of ServiceUI would be to add two new methods to ServiceRegistrar. This suggestion was somewhat controversial, in part because some felt we were trying to fulfill special requirements of finding UIs by changing ServiceRegistrar. When in reality, these additional methods I proposed have nothing to do with UI per se. They have to do with enabling resource-poor (especially memory-poor) clients to interact with the lookup service in general. I concluded that the options of resource-poor clients are quite limited given the current ServiceRegistrar interface no matter what the clients are trying to do with the lookup service. I came to this realization while thinking about ServiceUI, but it is a general problem, not one that's exclusive to getting a UI for a service or narrowing a search for services based on UIs offered.

The two proposed methods

Here are the two methods I suggested we add to ServiceRegistrar to empower resource-poor clients. The first is the getServiceIDs() method:

public ServiceIDMatches getServiceIDs(ServiceTemplate tmpl,
    int maxMatches) throws java.rmi.RemoteException;
[This method] looks at all service items that match the specified template, and returns at most maxMatches ServiceIDs from that set of matching service items, plus the total number of entries that matched. The return value is never null, and the returned ServiceID array is only null if maxMatches is zero. Duplicate entries are not possible because the lookup service enforces that each registered service has a unique ServiceID. The order of ServiceIDs within the returned array is arbitrary. An empty array is returned if no such entries or matching items exist. If a returned ServiceID cannot be deserialized (this should never happen), that element of the returned array is set to null and no exception is thrown.

Here is the ServiceIDMatches class:

package net.jini.core.lookup;

public class ServiceIDMatches extends java.lang.Object
    implements java.io.Serializable {

    public ServiceID[] ids;
    public int totalMatches;
}

The second one I suggested is the getEntries() method:

public EntryMatches getEntries(ServiceTemplate tmpl,
    Entry[] entryTmpl, int maxMatches) throws java.rmi.RemoteException;
[This method] looks at all service items that match the specified template, finds every entry (among those service items) that matches any entry template that appears in the entryTmpl field, and returns at most maxMatches entries from that set of matching entries, plus the total number of entries that matched. The return value is never null, and the returned Entry array is only null if maxMatches is zero. Duplicate entries are eliminated, and the order of entries within the returned array is arbitrary. An empty array is returned if there are no such entries or no matching items. If a returned class cannot be deserialized, it is set to null and no exception is thrown.

Here's the EntryMatches class:

package net.jini.core.lookup;

public class EntryMatches extends java.lang.Object
    implements java.io.Serializable {

    public Entry[] entries;
    public int totalMatches;
}

Using the proposed methods

The idea of these two browsing methods, getServiceIDs() and getEntries(), is to enable resource-poor clients to control how many objects get returned from lookup service. A resource-poor client needs to be able to trade off memory consumed by returned objects against the number of lookup or browsing method invocations it must call on ServiceRegistrar. I call the proposed methods "browsing methods" because they don't return a service object, just information (attributes and service IDs) about services that match a template. Thus, only methods named lookup() return an actual service object. The browsing methods -- including the already existing browsing methods getServiceTypes(), getEntryClasses, and getFieldValues() -- enable clients to narrow their search before getting an actual service object via a lookup() method.

lookup() and getServiceIDs()

The lookup() method that returns a ServiceMatches object enables a client to find out the total number of matches, and to control the maximum number of service items returned. A resource-poor client could use this method, if it is willing to download an entire service item, by specifying a maxMatches of the number one. But if the returned service turns out to be inappropriate (which it discovers only after looking through its attributes locally) it must do another lookup(). Unfortunately, the client can't say "give me a different match." It must simply hope that the next time it says one in the maxMatches field and calls lookup(), it is given a different service. If it is not given a different service, it must say two in the maxMatches field.

Now if the new service obtained with a maxMatches of two doesn't work, the client must requery with a maxMatches of three, and so forth. But what's worse, all this discussion assumes the resource-poor client is willing to download entire service items, which I believe is a questionable assumption.

If the resource-poor client had a getServiceIDs() method, as I defined previously, it could get back a set of service IDs for services that match any template. It can bound the number of service IDs by specifying some maximum in maxMatches. Since class ServiceID is final, no JAR files would need to be downloaded to the client.

Once the client has a list of matching service IDs, it can use the other browsing methods, such as getEntries(), getEntryClasses, and getFieldValues to look for the best service or UI for a service. The client can do this by looking at one registered service at a time (by specifying in turn each of the service IDs it got back from getServiceIDs) until it finds the appropriate service or UI. In other words, getServiceIDs() returns an array that can serve as a client's "to-do list" of further queries to make, each of which involves only one service item.

getEntries() and getFieldValues()

The getEntries() method enables clients to get ahold of entire entries, without requiring that the client download all the entries in the service item (as is done with the two-parameter lookup() method, which returns a ServiceMatches). It also enables clients to limit the maximum number of entries returned.

Although clients can grab fields, one named field at a time, via the getFieldValues() method, this approach has three potential problems. First, if a client wants an entire entry, getFieldValues() requires more method invocations (and therefore more network hits) than getEntries(), because the client must invoke getFieldValues() for each field of the entry.

Second, it is not always possible to assemble an entire entry from the objects returned by getFieldValues(). For example, imagine I invoke getFieldValues() twice with the same template and setIndex. First I set my field parameter to "name", and I get back the values "Bob" and "Sally". Next time I set the field parameter to "hobby", and I get back the values "fishing" and "stamp collecting". First of all, I don't know how many matching entries had any of these field values, because duplicates are eliminated. But even assuming only two entries matched my query, I'm still unable to assemble these two entries. I know one entry has a name field of "Bob" and the other has a name field of "Sally". I also know that one entry has a hobby field of "fishing" and the other has a hobby field of "stamp collecting". The trouble is, because these objects are returned in arbitrary order, I don't know whether Bob likes fishing or stamp collecting. Likewise, I don't know whether Sally likes fishing or stamp collecting. All I know is that if Sally likes fishing, then Bob likes stamp collecting, and vice versa. Thus, I can't reconstruct entries from the objects returned by getFieldValues(). Although I can get at individual attributes with getFieldValues(), I am unable to get information about the relationships between the various attributes collected into a single attribute set.

The third problem with getFieldValues() is that it only returns fields. Because Entrys are just objects that adhere to some conventions, they can contain methods as well as fields. To access those methods, a client needs to get the entire entry. Currently, the only way for a client to access a method declared in an Entry of the attribute sets is to use the lookup() that returns a ServiceMatches object, which includes all the entries for all the returned service items.

The helper service alternative

Rather than changing ServiceRegistrar, we could define a helper service interface that offers the two browsing methods, getEntries() and getServiceIDs(), and register it as a service. Low-resource clients could perform their queries via the helper service. Nevertheless, my sense is that these two browsing methods offer a fundamental enough service to low-resource clients that they (or something like them) belong in ServiceRegistrar.

The Jini team's response

In December 1999, members of the Jini team at Sun met to discuss the current state of the ServiceUI project and write a response to the ServiceUI proposal. By some twist of fate, the Jini team picked up an old copy of the ServiceUI proposal and as a result did not see the case made for two new ServiceRegistrar methods. Nevertheless, the Jini team did address the low-resource client issue in their response. They stated that they felt "a careful treatment of codebase issues will go a long way towards the treatment of limited resource devices." Jim Waldo, chief Jini architect at Sun, stated in his ServiceUI response that:

With [a proper] separation of codebases, our belief is that for now, we can reasonably assume that clients of all kinds generally should be able to download an entire ServiceItem from the lookup service, and that the complexities of the severely resource-constrained client case can be put off until such time as the issue of resource constraints can be dealt with in a broader context, with a more complete set of requirements.

In effect, the Jini team said that for the time being at least, we should assume that any client will be able to download an entire service item. To facilitate this goal in ServiceUI, the Jini team suggested we make sure the class files for UIs can be kept in separate JAR files from those of the service object and attribute sets. This separation helps reduce the memory required to download an entire service item, because a client need not download a UI's class files unless the client actually plans to use the UI.

Upon reading the Jini team's ServiceUI response, I decided I liked the assumption that any Jini client should be able to download an entire service item. The upcoming Jini release will include a ServiceUI specification and the first batch of ServiceUI code. In this release, we'll assume all clients can download any service item in its entirety, and make sure UI codebases can be kept separate from the service object and attribute set codebases.

However, it may still be desirable to eventually add functionality such as that provided by the two browsing methods proposed in this article. Given the current ServiceRegistrar interface, even if a low-resource client is able to download any service item in its entirety, it may not be able to find a desired service by downloading only one service item at a time.

Conclusion

In this column, I want not only to describe Jini technology as it currently exists, but also to encourage thinking and discussion in the Jini community about how the technology should evolve. This article describes the power and flexibility provided by the ServiceRegistrar interface to the Jini lookup service, and pointed out some of its limitations.

To discuss the material presented in this article, visit the discussion forum at: http://www.artima.com/jini/jf/lookup/index.html

Resources

This article was first published under the name Finding services with the Jini lookup service in JavaWorld, a division of Web Publishing, Inc., February 2000.

Talk back!

Have an opinion? Be the first to post a comment about this article.

About the author

Bill Venners is president of Artima Software, Inc. and editor-in-chief of Artima.com. He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Bill has been active in the Jini Community since its inception. He led the Jini Community's ServiceUI project that produced the ServiceUI API. The ServiceUI became the de facto standard way to associate user interfaces to Jini services, and was the first Jini community standard approved via the Jini Decision Process. Bill also serves as an elected member of the Jini Community's initial Technical Oversight Committee (TOC), and in this role helped to define the governance process for the community. He currently devotes most of his energy to building Artima.com into an ever more useful resource for developers.