Discovering a Lookup Service

A client locates a service by querying a lookup service. In order to do this, it must first locate such a service. On the other hand, a service must register itself with the lookup srvice, and in order to do so it must also first locate a service.

The initial phase of both a client and a service is thus discovering a lookup service. Such a service (or set of services) will usually have been started by some independent mechanism. The search for a lookup service can be done either by unicast or by broadcast.

1. Unicast discovery

Unicast discovery can be used when you already know the machine on which the lookup service resides, so you can ask for it directly. This is expected to be used for a service that is outside of your local network, which you know the address of anyway (possibly advertised in some newsgroup, or maybe even on TV!). The class LookupLocator in package net.jini.core.discovery is used for this. There are two constructors,


package net.jini.core.discovery;

public Class LookupLocator {

    LookupLocator(java.lang.String url)
                  throws java.net.MalformedURLException;
    LookupLocator(java.lang.String host,int port);
}
For the first constructor, the URL must be of the form jini://host/ or jini://host:port/. If no port is given, it defaults to 4160. No unicast discovery is performed at this stage, though.

The following program creates some objects with valid and invalid host/URLs. They are only checked for syntactic validity rather than existence as URLs:



import net.jini.core.discovery.LookupLocator;

/**
 * InvalidLookupLocator.java
 *
 *
 * Created: Tue Mar  9 14:14:06 1999
 *
 * @author Jan Newmarch
 * @version
 */

public class InvalidLookupLocator  {

    static public void main(String argv[]) {
	new InvalidLookupLocator();
    }
   
    public InvalidLookupLocator() {
	LookupLocator lookup;

	// this is valid
	try {
	    lookup = new LookupLocator("jini://localhost");
	    System.out.println("First lookup creation succeeded");
	} catch(java.net.MalformedURLException e) {
	    System.err.println("First lookup failed: " + e.toString());
	}

	// this is probably an invalid URL, 
	// but the URL is syntactically okay
	try {
	    lookup = new LookupLocator("jini://ABCDEFG.org");
	    System.out.println("Second lookup creation succeeded");
	} catch(java.net.MalformedURLException e) {
	    System.err.println("Second lookup failed: " + e.toString());
	}

	// this IS a malformed URL
	try {
	    lookup = new LookupLocator("A:B:C://ABCDEFG.org");
	    System.out.println("Third lookup creation succeeded");
	} catch(java.net.MalformedURLException e) {
	    System.err.println("Third lookup failed: " + e.toString());
	}

	// this is valid
	lookup = new LookupLocator("localhost", 80);
	System.out.println("Fourth lookup creation succeeded");
    }
    
} // InvalidLookupLocator

This program might not run as is, due to security issues. If that is the case, see the chapter on Security.

Search and lookup is performed by the method getRegistrar() of the LookupLocator which returns an object of class ServiceRegistrar.


public ServiceRegistrar getRegistrar()
                              throws java.io.IOException,
                                     java.lang.ClassNotFoundException
The ServiceRegistrar is discussed in detail later. By this stage, the program looks like


import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;

/**
 * UnicastRegistrar.java
 *
 *
 * Created: Fri Mar 12 22:34:53 1999
 *
 * @author Jan Newmarch
 * @version
 */

public class UnicastRegistrar  {
    
    static public void main(String argv[]) {
        new UnicastRegistrar();
    }
   
    public UnicastRegistrar() {
	LookupLocator lookup = null;
	ServiceRegistrar registrar = null;

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

	// the code takes separate routes from here for client or service
    }
   
} // UnicastRegistrar

2. Broadcast discovery

If the location of a lookup service is unknown, it is neccessary to make a broadcast search for one. The class LookupDiscovery in package net.jini.discovery is used for this. There is a single constructor


LookupDiscovery(java.lang.String[] groups)

The parameter to the LookupDiscovery constructor can take three cases

??? What is a group ???

2.1 DiscoveryListener

A broadcast is an open-ended call across the (local??? What is the boundary of search???) network, expecting lookup services to reply as they receive it. Doing so may take time, and there will generally be an unknown number of lookup services that can reply. To handle this indeterminacy, the LookupDiscovery object can have a listener registered with it that is invoked as each reply comes in.


public void addDiscoveryListener(DiscoveryListener l)
The listener must implement the DiscoveryListener interface:

package net.jini.discovery;

public abstract interface DiscoveryListener {
    public void discovered(DiscoveryEvent e);
    public void discarded(DiscoveryEvent e);
}

The discovered() method is invoked whenever a lookup service has been discovered. The API recommends that this method should return quickly, and not make any remote calls. However, for a service it is the natural place to register the service, and for a client it is the natural place to ask if there is a service available and to invoke this service. It may be better to perform these lengthy operations in a separate thread.

There are other timing issues involved: when the DiscoveryListener is created, the broadcast is made. After this, a listener is added to this discovery object. What happens if replies come in very quickly, before the listener is added? The specification guarantees that these replies will be buffered and not lost (is there a limit to the buffer size???). Conversely, no replies may come in for a long time - what is the application supposed to do in the meantime? It cannot simply exit, because then there would be no object to reply to! it has to be made persistent enough to last till replies come in. One way of handling this is if the application has a GUI interface - then it will stay till the user dismisses it. Another is to go to sleep for, say, a thousand seconds.

The discarded() method is invoked whenever a lookup service is discarded (meaning ???).

2.2 DiscoveryEvent

The parameter to the discover()method is a DiscoveryEvent object.

package net.jini.discovery;

public Class DiscoveryEvent {
    public net.jini.core.lookup.ServiceRegistrar[] getRegistrars();
}
This has one public method, getRegistrars() which returns an array of ServiceRegistrar objects. (How can more than one be returned???). Each one of these is the same class as the object returned from a unicast search for a lookup service.

By this stage the program looks like



import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;

/**
 * MulticastRegistrar.java
 *
 *
 * Created: Fri Mar 12 22:49:33 1999
 *
 * @author Jan Newmarch
 * @version
 */

public class MulticastRegistrar implements DiscoveryListener {
 
    static public void main(String argv[]) {
        new MulticastRegistrar();
    }
      
    public MulticastRegistrar() {
        LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
	    System.exit(1);
        }

        discover.addDiscoveryListener(this);

	// stay around long enough to receive replies
	try {
	    Thread.currentThread().sleep(1000000L);
	} catch(java.lang.InterruptedException e) {
	    // do nothing
	}
    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();

        for (int n = 0; n < registrars.length; n++) {
	    ServiceRegistrar registrar = registrars[n];

	    // the code takes separate routes from here for client or service
  	}
    }

    public void discarded(DiscoveryEvent evt) {

    }
} // MulticastRegistrar

3. ServiceRegistrar

The ServiceRegistrar is an abstract class which is implemented by each lookup service. The actual details of this implementation are not relevant here. The role of a ServiceRegistrar is to act as a proxy for the lookup service. This proxy runs in the application, which may be a service or a client.

This is the first object that is moved from one Java process to another in Jini. It is shipped from the lookup service to the application looking for the lookup service, using RMI. From then it runs as an object in the application's address space, and the application makes normal method calls to it. When needed, it communicates back to its lookup service, but does so without explicit RMI calls being needed by the application. Presumably it caches some info about the lookup service - exactly what??? The set of services???

This object has two major methods, one used by a service attempting to register


public ServiceRegistration register(ServiceItem item,
                                    long leaseDuration)
                             throws java.rmi.RemoteException
and the other(s) by a client trying to locate a particular service

public java.lang.Object lookup(ServiceTemplate tmpl)
                        throws java.rmi.RemoteException;
public ServiceMatches lookup(ServiceTemplate tmpl,
                             int maxMatches)
                      throws java.rmi.RemoteException;
The details of these are given later. For now, an overview will suffice.

A service will register an object (that is, an instance of a class), and a set of attributes for that object. For example, a printer may specify that it can handle Postscript documents, or a toaster that it can deal with frozen slices of bread. The service may register itself, or it may register a proxy that can act on its behalf. Note carefully: the registered object will be shipped around using RMI. When it finally gets to run, it may be a long way away from where it was originally created.

A client is trying to find a service, using some properties of the service that it knows about. Whereas the service can export a live object, the client cannot use a service object as a property - because then it would already have the thing, and wouldn't need to try to find one! What it can do is to use a class object, and try to find if there are any instances of this class lying around the network. In addition, it may specify a set of attribute values that it requires from the service.

The next step is to look at the possible forms of attribute values, and how matching will be performed. This is done using Jini Entry objects. The simplest services, and the least demanding clients, will not require any attributes: the Entry[] array will be null. You may wish to skip ahead to service registration or to client search

This file is Copyright ©Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name

The copyright is the OpenContent License (http://www.opencontent.org/opl.shtml), which is the ``document'' version of the GNU OpenSource license.