This chapter looks at what is involved in discovering a lookup service/service locator. This is common to both services and clients.
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 service, 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.
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 lookup 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.
The host should be localhost
or some other
valid DNS name.
No unicast discovery is performed at this stage, though, so any
rubbish could be entered. Only a check for syntactic validity is performed.
This is not even done for the second constructor.
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
All programs will need to be compiled using the JDK 1.2 compiler.
The CLASSPATH
will need to include the Jini files
reggie.jar
and jini-core.jar
for
compilation of the source code. When a service is run, these Jini
files will need to be in its CLASSPATH
. Similarly,
when a client runs, it will also need these files in its
CLASSPATH
. And finally, when a lookup service is run
it too will need these files in its CLASSPATH
. The
reason for this repetition is that the service, the client and
lookup service all three separate applications, running in three
separate Java Virtual Machines, and quite likely will be on
three separate computers. They will each have separate requirements,
but at least this is common to all three!
The InvalidLookupLocator
has no additional requirements.
It does not perform any network calls, and does not require any
additional service to be running. So it can be run simply by
java -classpath ... InvalidLookupLocator
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
This program might not run as is, due to security issues. If that is the case, see the chapter on Security.
The program needs to be compiled and run with reggie.jar
and jini-code.jar
in its CLASSPATH
.
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, the program will throw an exception and terminate.
This program will receive a ServiceRegistrar
from the
service locator. However, it does so by a simple
readObject()
on a socket connected to the service
locator, and so does not need any
additional support services such as rmiregistry
.
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
null
, or LookupDiscovery.ALL_GROUPS
,
means to attempt to discover all reachable lookup services.
This will be the normal case.
LookupDiscovery.NO_GROUPS
,
means that the object is created, but no search is performed. In this
case, the method setGroups()
will need to be called
in order to perform a search.
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 ``Jini Discovery Utilities 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 ???).
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());
e.printStackTrace();
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
System.out.println("found a service locator");
}
}
public void discarded(DiscoveryEvent evt) {
}
} // MulticastRegistrar
The program needs to be compiled and run with reggie.jar
and jini-code.jar
in its CLASSPATH
.
When run, it will attempt to find all service locators that it
can. If there are none, it will find none - pretty boring.
So one or more should be set running in the near network or on the
local machine.
How far away can they be???.
This program will receive ServiceRegistrar
's from the
service locators. However, it does so by a simple
readObject()
on a socket connected to a service
locator, and so does not need any
additional support services such as rmiregistry
.
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 a socket connection. 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. It will have been created
in the service's JVM, transferred to the lookup locator by
register()
and then to the client's JVM by
lookup()
.
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 in service locators. 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