Contents
This chapter looks at a simple problem, implementing it in a number of different ways
Applications often need to work out the type of a file, to see if it is a text file, an HTML document, an executable, etc. This can be done in two ways:
file
command use the
second method, and have a complex description file (such
as /etc/magic
or /usr/share/magic
)
to aid in this. Many other applications such as Web browsers,
mail readers (and even some operating systems!) use the first
method and work out a file's type based on its name.
A common file classification is into MIME types such as
text/plain
and image/gif
. There
are tables of ``official'' MIME types (unofficial ones can
be added on an adhoc basis), and there
are also tables of mappings from filename endings to corresponding
MIME types. These tables have entries such as
application/postscript ai eps ps
application/rtf rtf
application/zip zip
image/gif gif
image/jpeg jpeg jpg jpe
text/html html htm
text/plain txt
and are stored in files for applications to access.
This storage of tables separate from the applications that would use them is rated as bad from the O/O point of view, since each application would need to have code to interpret the tables. The multiplicity of these tables and the ability of users to modify them makes this a maintenance problem. It would be better to encapsulate at least the filename to MIME type mapping table in an object. We define a MIME class
package standalone;
/**
* MIMEType.java
*
*
* Created: Mon Mar 15 22:09:13 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package standalone
*/
public class MIMEType {
/**
* A MIME type is made up of 2 parts
* contentType/subtype
*/
protected String contentType;
protected String subtype;
public MIMEType(String type) {
int slash = type.indexOf('/');
contentType = type.substring(0, slash-1);
subtype = type.substring(slash+1, type.length());
}
public MIMEType(String contentType, String subtype) {
this.contentType = contentType;
this.subtype = subtype;
}
public String toString() {
return contentType + "/" + subtype;
}
} // MIMEType
and a mapping class
package standalone;
/**
* FileClassifier.java
*
*
* Created: Mon Mar 15 22:18:59 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package standalone
*/
public class FileClassifier {
static MIMEType getMIMEType(String fileName) {
if (fileName.endsWith(".gif")) {
return new MIMEType("image", "gif");
} else if (fileName.endsWith(".jpeg")) {
return new MIMEType("image", "jpeg");
} else if (fileName.endsWith(".mpg")) {
return new MIMEType("video", "mpeg");
} else if (fileName.endsWith(".txt")) {
return new MIMEType("text", "plain");
} else if (fileName.endsWith(".html")) {
return new MIMEType("text", "html");
} else
// fill in lots of other types,
// but eventually give up and
return null;
}
} // FileClassifier
This mapping class has no constructors, as it justs acts as a
lookup table via its static method getMIMEType()
.
Applications may make use these classes as they stand, by simply
compiling with them and having the class files available at
runtime. This will still result in duplication throughout JVMs,
possible multiple copies of the class files, and potentially
severe maintenance problems if applications need to be re-compiled.
It may be better to have the FileClassifier
as a
network service. What will be involved in this?
If we wish to make a version of FileClassifier
available
across the network, there are a number of options
interface
,
whereas the server deals in terms of a Java class that implement
's
the interface
.
The client and any implementations of a service must share some common classes. For a file classification service the common classes are the classifier itself (which can be implemented as many different services) and the return value, the MIME type. These have to change very slightly from their standalone form.
The class MIMEType
is known to the client and to any file classifier
service. The class files can be expected to be known to the JVMs of all clients
and services. The method getMIMEType()
will return an object from
the service. There are implementation possibilities that can affect this object:
MIMEType
object
must be serialized for transport to the client JVM. For this to be possible,
it must implement the Serializable
interface
Serializable
then it can be used in both the
remote and local cases, but if it doesn't then it can only be used in the
local case.
Making decisions about interfaces based on future implementation concerns is traditionally rated as poor design. However, a document from Sun ... argues that in the case of distributed objects it is important to determine where the objects may be running (locally or remote) and adjust interfaces accordingly. This is to take into account possible extra failure modes of methods, and in this case, an extra requirement on the object.
This leads to an interface which adds the Serializable
interface
to the previous version.
package common;
import java.io.Serializable;
/**
* MIMEType.java
*
*
* Created: Wed Mar 17 14:17:32 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package common
*/
public class MIMEType implements Serializable {
/**
* A MIME type is made up of 2 parts
* contentType/subtype
*/
protected String contentType;
protected String subtype;
public MIMEType(String type) {
int slash = type.indexOf('/');
contentType = type.substring(0, slash-1);
subtype = type.substring(slash+1, type.length());
}
public MIMEType(String contentType, String subtype) {
this.contentType = contentType;
this.subtype = subtype;
}
public String toString() {
return contentType + "/" + subtype;
}
} // MIMEType
Changes have to be made to the file classifier interface as well.
Firstly, interfaces cannot have
static
methods, so we shall have to turn the method
getMIMEType()
into
a public instance method. In addition, instances implementing this may need to be
shipped around as proxy objects, so it should extend Serializable
.
This is the same situation as with MIMEType
.
In addition, all methods are defined to throw a java.rmi.RemoteException
.
This type of exception is used by Java (not just the RMI component) to mean
``a network error has occurred''. This could be a lost connection, a missing
server, a class not downloadable, etc.
There is a little subtlety here, related to the java.rmi.Remote
class: the methods of Remote
must all throw a
RemoteException
, but the converse is not true. If all the methods
throw RemoteException
, it does not mean the class implements/extends
Remote
. It only means that an implementation may
be implemented as a remote (distributed) object, and this implementation might also
use the RMI Remote
interface.
This gives the following interface
package common;
import java.io.Serializable;
/**
* FileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package common
*/
public interface FileClassifier extends Serializable {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException;
} // FileClasssifier
Why does this interface throw a java.rmi.RemoteException
in the
getMIMEType()
method? Well, an interface is supposed to be above
all possible implementations, and should never change. The implementation
discussed in this section does not throw such an exception. However, the
implementation in the next section uses an RMI implementation,
and this will require that the method throws an java.rmi.RemoteException
.
I'm not sure that I really like this, designing features of the interface in order
to fit just one of many possible implementations,
but if we don't put the exception in here then we can't just add it later!
The client is the same for all of the possibilities on the server
discussed in later sections and in later chapters. It does not
care how the server-side implementation is done, just as long as it
gets a service that it wants, and it specifies this by asking
for a FileClassifier
interface.
If there is a known service locator which will know about the service,
then there is no need to search. This doesn't mean that the location of
the service is known, only of the locator. For example, there may be
a (fictitious) organisation ``All About Files'' at
www.all_about_files.com
that
would know about various file services, keeping track of them as they come
on line, move, disappear, etc. A client would ask
the service locator running on this site for the service, wherever it is.
This uses the unicast lookup techniques.
package client;
import common.FileClassifier;
import common.MIMEType;
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.rmi.RMISecurityManager;
import net.jini.core.lookup.ServiceTemplate;
/**
* TestUnicastFileClassifier.java
*
*
* Created: Wed Aug 04
*
* @author Jan Newmarch
* @version 1.0
*/
public class TestUnicastFileClassifier {
public static void main(String argv[]) {
new TestUnicastFileClassifier();
}
public TestUnicastFileClassifier() {
LookupLocator lookup = null;
ServiceRegistrar registrar = null;
FileClassifier classifier = null;
try {
lookup = new LookupLocator("jini://www.all_about_files.com");
} catch(java.net.MalformedURLException e) {
System.err.println("Lookup failed: " + e.toString());
System.exit(1);
}
System.setSecurityManager(new RMISecurityManager());
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);
}
Class[] classes = new Class[] {FileClassifier.class};
ServiceTemplate template = new ServiceTemplate(null, classes, null);
try {
classifier = (FileClassifier) registrar.lookup(template);
} catch(java.rmi.RemoteException e) {
e.printStackTrace();
System.exit(1);
}
if (classifier == null) {
System.out.println("Classifier null");
System.exit(2);
}
MIMEType type;
try {
type = classifier.getMIMEType("file1.txt");
System.out.println("Type is " + type.toString());
} catch(java.rmi.RemoteException e) {
System.err.println(e.toString());
}
// System.exit(0);
}
} // TestUnicastFileClassifier
The client's JVM looks like
ServiceRegistrar
and FileClassifier
, but it doesn't know - or need to know -
what classes they are.
When the service locator's JVM is added in, it looks like
registrar
from the
JVM of the service locator. This object is not specified in detail. Using the
reggie
service locator, the classes which implement this object
are contained in the file reggie-dl.jar
and are downloaded
using (typically) an HTTP server.
More likely, client will need to search through all
of the service locators till it finds one holding a service it
is looking for. It would need to use a multicast search for this.
If it only needs one occurrence of the service, then it can do something like
exit after using the service. More complex behaviour will be illustrated
in later examples.
The client does not need to have long-term persistence.
But it does need a user thread to remain in existence for long
enough to find service locators and find a suitable service.
So in main()
a user thread again sleeps for a short
period (ten seconds).
package client;
import common.FileClassifier;
import common.MIMEType;
import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
/**
* TestFileClassifier.java
*
*
* Created: Wed Mar 17 14:29:15 1999
*
* @author Jan Newmarch
* @version 1.3
* moved sleep() from constructor to main()
* moved to package client
* simplified Class.forName to Class.class
*/
public class TestFileClassifier implements DiscoveryListener {
public static void main(String argv[]) {
new TestFileClassifier();
// stay around long enough to receive replies
try {
Thread.currentThread().sleep(100000L);
} catch(java.lang.InterruptedException e) {
// do nothing
}
}
public TestFileClassifier() {
System.setSecurityManager(new RMISecurityManager());
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
Class [] classes = new Class[] {FileClassifier.class};
FileClassifier classifier = null;
ServiceTemplate template = new ServiceTemplate(null, classes,
null);
for (int n = 0; n < registrars.length; n++) {
System.out.println("Service found");
ServiceRegistrar registrar = registrars[n];
try {
classifier = (FileClassifier) registrar.lookup(template);
} catch(java.rmi.RemoteException e) {
e.printStackTrace();
System.exit(2);
}
if (classifier == null) {
System.out.println("Classifier null");
continue;
}
MIMEType type;
try {
type = classifier.getMIMEType("file1.txt");
System.out.println("Type is " + type.toString());
} catch(java.rmi.RemoteException e) {
System.err.println(e.toString());
}
// System.exit(0);
}
}
public void discarded(DiscoveryEvent evt) {
// empty
}
} // TestFileClassifier
The file classifier service does not rely on any particular properties of its host - it is not hardware or operating system dependant, for example. In this case it is possible to upload the entire service to the client and let it run there. No proxy is required.
The implementation of this is straightforward
package option2;
import common.MIMEType;
import common.FileClassifier;
/**
* FileClassifierImpl.java
*
*
* Created: Wed Mar 17 14:22:13 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class FileClassifierImpl implements FileClassifier {
public MIMEType getMIMEType(String fileName) {
if (fileName.endsWith(".gif")) {
return new MIMEType("image", "gif");
} else if (fileName.endsWith(".jpeg")) {
return new MIMEType("image", "jpeg");
} else if (fileName.endsWith(".mpg")) {
return new MIMEType("video", "mpeg");
} else if (fileName.endsWith(".txt")) {
return new MIMEType("text", "plain");
} else if (fileName.endsWith(".html")) {
return new MIMEType("text", "html");
} else
// fill in lots of other types,
// but eventually give up and
return null;
}
public FileClassifierImpl() {
// empty
}
} // FileClassifierImpl
The server for this needs to create an instance of the exportable
service, register this and keep the lease alive.
In the discovered()
method
it not only registers the service but also adds it to a
LeaseRenewalManager
, to keep the lease alive ``forever''.
This manager runs its own threads to keep re-registering the leases,
but these are daemon threads. So in the main()
method
the user thread goes to sleep for as long as we want the server
to stay around. Note that if the server does terminate, then the lease
will fail to be renewed and the exported service will be discarded
from lookup locators even though the server is not required for delivery
of the service.
package option2;
import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;
/**
* FileClassifierServer.java
*
*
* Created: Wed Mar 17 14:23:44 1999
*
* @author Jan Newmarch
* @version 1.1
* added LeaseRenewalManager
* moved sleep() from constructor to main()
*/
public class FileClassifierServer implements DiscoveryListener,
LeaseListener {
protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
public static void main(String argv[]) {
new FileClassifierServer();
// keep server running forever to
// - allow time for locator discovery and
// - keep re-registering the lease
try {
Thread.currentThread().sleep(Lease.FOREVER);
} catch(java.lang.InterruptedException e) {
// do nothing
}
}
public FileClassifierServer() {
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
ServiceItem item = new ServiceItem(null,
new FileClassifierImpl(),
null);
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.println("Register exception: " + e.toString());
}
System.out.println("service registered");
// set lease renewal in place
leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
}
}
public void discarded(DiscoveryEvent evt) {
}
public void notify(LeaseRenewalEvent evt) {
System.out.println("Lease expired " + evt.toString());
}
} // FileClassifierServer
The server by itself running in its JVM looks like
ServiceRegistrar
from the service locator (such as reggie
).
If we add in the service locator and the client in their JVM's, it becomes
FileClassifier
object in the client is here supplied by the
service object FileClassifierImpl
.
The client for this service was discussed earlier. The client does not need any special information about this implementation of the service and so can remain quite generic.
We have the classes
common.MIMEType
common.FileClassifier
option2.FileClassifierImpl
option2.FileClassifierServer
client.TestFileClassifier
FileClassifier
TestFileClassifier
FileClassifierImpl
to clients
The server running FileClassifierServer
needs to know the
following classes and interfaces
common.FileClassifier
interface
common.MIMEType
option2.FileClassifierServer
option2.FileClassifierImpl
CLASSPATH
of the server.
The class option2.FileClassifierImpl
will need to be accessible to an HTTP server, as discussed in the next
section.
The lookup service does not need to know any
of these classes. It just deals with them in the form of a
java.rmi.MarshalledObject
The client needs to know
common.FileClassifier
interface
common.MIMEType
client.TestFileClassifier
We now have a service FileClassifierServer
and a client
TestFleClassifier
to run. There should also be at least
one lookup locator already running.
The CLASSPATH
should be set for each to include
the classes discussed in the last section, in addition to the
standard ones.
A serialized instance of option2.FileClassifierImpl
will be passed from the server to the locator and then to the client.
Once on the client, it will need to be able to run the class file
for this object, so will need to load its class file from an HTTP
server. The location of this class file relative to the server's
DocumentRoot
will need to be specified by the service
invocation. For example, if it is stored in
/DocumentRoot/classes/option2/FileClassifierImp.class
then the service will be started by:
java -Djava.rmi.codebase=http://hostname/classes \
option2.FileClassifierServer
In this, hostname
is the name of the host the
server is running on. Note that this cannot
be localhost
, because the local host for the server
will not be the local host for the client!
The client will be loading a class definition across the network. It will need to allow this in a security policy file by
java -Djava.security.policy=policy.all client.TestFileClassifier
The code (and subsequent discussion) in this section were based on the
timeservice
code from Eran Davidov at
www.artima.com/jini/resources/timeservice.html
. Howsever, the code and descriptions have evolved since then.
The third option was to upload a proxy to the service locator and eventually
to clients, while leaving a ``real'' file classifier running on the
original service machine, as in
The situation is more complex than this figure shows. It is easy enough to get the proxy over to the client, by just using the techniques of the last example. When the proxy runs, it must be able to run the ``real'' service back on its original server. But how does it find it? And how does it invoke it? There is no point in using Jini for this again, since we would just end up in a loop (or, more likely, a mess). Instead, we need to use a different mechanism for the proxy to communicate to its service. Any suitable protocol could be used, but Java supplies a ready-made one, in RMI. So in general, the proxy will access and invoke the ``real'' service using RMI calls.
In order for FileClassifierImpl
(which is running on the server)
to be accessed from the client, the class must
implement the RMI Remote
interface.
In addition, it will need to implement the FileClassifer
interface.
It is convenient to define an intermediate interface
RemoteFileClassifier
.
The implementation class will need to export the implementation
object to the RMI runtime, and
an easy way to get all the hard work done for this is to inherit from
UnicastRemoteObject
.
The FileClassifierProxy
must be aware of this service
so that it can invoke it, so it has a reference to the implementation.
This must all be
invisible to the client, which just wants to know about
FileClassifier
.
This gives a class diagram of
The FileClassifier
interface remains unchanged as
package common;
import java.io.Serializable;
/**
* FileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package common
*/
public interface FileClassifier extends Serializable {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException;
} // FileClasssifier
The FileClassifier
interface remains unchanged as
package common;
import java.io.Serializable;
/**
* FileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package common
*/
public interface FileClassifier extends Serializable {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException;
} // FileClasssifier
The class MIMEType
remains unchanged as
package common;
import java.io.Serializable;
/**
* MIMEType.java
*
*
* Created: Wed Mar 17 14:17:32 1999
*
* @author Jan Newmarch
* @version 1.1
* moved to package common
*/
public class MIMEType implements Serializable {
/**
* A MIME type is made up of 2 parts
* contentType/subtype
*/
protected String contentType;
protected String subtype;
public MIMEType(String type) {
int slash = type.indexOf('/');
contentType = type.substring(0, slash-1);
subtype = type.substring(slash+1, type.length());
}
public MIMEType(String contentType, String subtype) {
this.contentType = contentType;
this.subtype = subtype;
}
public String toString() {
return contentType + "/" + subtype;
}
} // MIMEType
Finally, the client doesn't change either.
The interface RemoteFileClassifier
just adds the
Remote
interface
package option3;
import common.FileClassifier;
import java.rmi.Remote;
/**
* RemoteFileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public interface RemoteFileClassifier extends FileClassifier, Remote {
} // RemoteFileClasssifier
The class FileClassifierImpl
just changes its
inheritance:
package option3;
import java.rmi.server.UnicastRemoteObject;
import common.MIMEType;
import common.FileClassifier;
/**
* FileClassifierImpl.java
*
*
* Created: Wed Mar 17 14:22:13 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class FileClassifierImpl extends UnicastRemoteObject
implements RemoteFileClassifier {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException {
System.out.println("Called with " + fileName);
if (fileName.endsWith(".gif")) {
return new MIMEType("image", "gif");
} else if (fileName.endsWith(".jpeg")) {
return new MIMEType("image", "jpeg");
} else if (fileName.endsWith(".mpg")) {
return new MIMEType("video", "mpeg");
} else if (fileName.endsWith(".txt")) {
return new MIMEType("text", "plain");
} else if (fileName.endsWith(".html")) {
return new MIMEType("text", "html");
} else
// fill in lots of other types,
// but eventually give up and
return new MIMEType(null, null);
}
public FileClassifierImpl() throws java.rmi.RemoteException {
// empty
}
} // FileClassifierImpl
The FileClassifierProxy
is a little more complex and
will be discussed soon.
The server has a more complex task. It has to perform the following steps
FileClassifierImpl
.
(This is automatically exported to the RMI runtime, as it extends
UnicastRemoteObject
.)
FileClassifierProxy
with knowledge of
the FileClassifierImpl
, passed in as a parameter
to the constructor
FileClassifierProxy
object to the
location service
FileClassifierImpl
.
However, the RMI runtime will have ensured that what is in the exported proxy
is actually an RMI stub
(of type FileClassifierImpl_Stub
)
which can run on the client
side and refer back to the real implementation object.
This is all done by
package option3;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;
import java.rmi.RMISecurityManager;
/**
* FileClassifierServer.java
*
*
* Created: Wed Mar 17 14:23:44 1999
*
* @author Jan Newmarch
* @version 1.2
* added LeaseRenewalManager
* moved sleep() from constructor to main()
* replaced FCImpl name in RCProxy with actual object,
* and got rind of Naming() etc
*/
public class FileClassifierServer implements DiscoveryListener, LeaseListener {
// this is just a name - can be anything
// impl object forces search for Stub
static final String serviceName = "FileClassifier";
protected FileClassifierImpl impl;
protected FileClassifierProxy proxy;
protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
public static void main(String argv[]) {
new FileClassifierServer();
// no need to keep server alive, RMI will do that
}
public FileClassifierServer() {
try {
impl = new FileClassifierImpl();
} catch(Exception e) {
System.err.println("New impl: " + e.toString());
System.exit(1);
}
// set RMI scurity manager
System.setSecurityManager(new RMISecurityManager());
// make a proxy with the impl (will be made into an RMI stub)
proxy = new FileClassifierProxy(impl);
// now continue as before
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
for (int n = 0; n < registrars.length; n++) {
System.out.println("found registrars");
ServiceRegistrar registrar = registrars[n];
// export the proxy service
ServiceItem item = new ServiceItem(null,
proxy,
null);
ServiceRegistration reg = null;
try {
reg = registrar.register(item, Lease.FOREVER);
} catch(java.rmi.RemoteException e) {
System.err.print("Register exception: ");
e.printStackTrace();
// System.exit(2);
continue;
}
try {
System.out.println("service registered at " +
registrar.getLocator().getHost());
} catch(Exception e) {
}
leaseManager.renewFor(reg.getLease(), Lease.FOREVER, this);
}
}
public void discarded(DiscoveryEvent evt) {
}
public void notify(LeaseRenewalEvent evt) {
System.out.println("Lease expired " + evt.toString());
}
} // FileClassifierServer
There is a side-effect in registering the service with an
rmiregistry
. While this has a reference to the
service, the service will be kept alive and will not be garbage-collected.
This means that there is now no need for the application to keep
itself alive by sleeping, and the main()
method can
simply terminate.
Now we can look at the FileClassifierProxy
.
This is uploaded to the lookup locator, and then onto a client.
The client will deserialize this, and can save a reference to the RMI
stub object for the real service.
The real service is an instance of FileClassifierImpl
running on the server machine. The proxy is an instance of the
FileClassifierProxy
running on the client. The proxy
also has a reference to an ``implementation'' object.
The RMI runtime will have arranged for this to be actually an RMI
reference stub, which in fact is
another proxy - the RMI proxy for the FileClassifierImpl
!
This RMI stub is of type FileClassifierImpl_Stub
and is generated by rmic
, as described later.
This double layer of proxies/stubs is caused by the use of RMI to access
the FileClassifierImpl
.
Later, when the client calls getMIMEType()
on the
FileClassifierProxy
it will invoke the same method
on the RMI proxy stub which will send a remote call to the
real server to do this:
package option3;
import common.FileClassifier;
import common.MIMEType;
import java.io.Serializable;
import java.io.IOException;
import java.rmi.Naming;
/**
* FileClassifierProxy
*
*
* Created: Thu Mar 18 14:32:32 1999
*
* @author Jan Newmarch
* @version 1.1
* simplified by using RMI stub object for impl instead of
* name of object to be looked up
*/
public class FileClassifierProxy implements FileClassifier, Serializable {
protected String serviceLocation;
RemoteFileClassifier server = null;
public FileClassifierProxy(FileClassifierImpl serv) {
this.server = serv;
if (serv==null) System.err.println("server is null");
}
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException {
if (server==null) System.err.println("server2 is null");
if (fileName==null) System.err.println("filename is null");
return server.getMIMEType(fileName);
}
} // FileClassifierProxy
We have the classes
common.MIMEType
common.FileClassifier
option3.RemoteFileClassifier
option3.FileClassifierImpl
option3.FileClassifierImpl_Stub
option3.FileClassifierProxy
option3.FileClassifierServer
client.TestFileClassifier
FileClassifierImpl_Stub
is added to our classes by
rmic
as discussed in the next section.)
These could be running on upto four different machines
FileClassifier
TestFileClassifier
The server running FileClassifierServer
needs to know the
following classes and interfaces
common.FileClassifier
interface
option3.RemoteFileClassifier
interface
common.MIMEType
option3.FileClassifierServer
option3.FileClassifierImpl
option3.FileClassifierProxy
The lookup service does not need to know any
of these classes. It just deals with them in the form of a
java.rmi.MarshalledObject
The client needs to know
common.FileClassifier
interface
common.MIMEType
In addition, the HTTP server needs to be able to load and store classes. It needs to be able to access
option3.FileClassifierImpl_Stub
interface
option3.RemoteFileClassifier
interface
common.FileClassifier
interface
common.MIMEType
FileClassifierProxy
constructor,
the class FileClassifierImpl
is passed in. The RMI runtime converts this to
FileClassifierImpl_Stub
. This class implements the same
interfaces as FileClassifierImpl
: that is,
RemoteFileClassifier
and hence FileClassifier
,
so these also need to be available. In the implementation,
FileClassifierImpl
references the class
MIMEType
, so this must also be available.
What does the phrase ``available'' mean in the last paragraph?
The HTTP server will look for files based on the
java.rmi.server.codebase
property of the application server. The value of this
property is a URL. Often, URLs can be file or
http references.
But for this case, the URL will be used by clients running anywhere,
so it cannot be a file reference specific to a particular machine.
For the same reason, it cannot be just localhost
- unless
you are running every part of a Jini federation on a single computer!
If java.rmi.server.codebase
is
an http reference, then the above class files must be accessible from
that reference. For example, suppose the property is set to
java.rmi.server.codebase=http://myWebHost/classes
(where myWebHost
is the name of the HTTP server's host)
and this Web server has its DocumentRoot
set to /home/webdocs
then these files must exist
/home/webdocs/classes/option3/FileClassifierImpl_Stub.class
/home/webdocs/classes/option3/RemoteFileClassifier.class
/home/webdocs/classes/common/FileClassifier.class
/home/webdocs/classes/common/MIMEType.class
Again we have a server and a client to run. Calling the client is unchanged from option2, with a security policy required.
java -Djava.security.policy=policy.all client.TestFileClassifier
The server is more complex, because the RMI runtime is manipulating
RMI stubs, and these have additional requirements.
Firstly, RMI stubs must be generated during compilation.
Secondly, security rights must be set since an
RMISecurityManager
is used.
Although the FileClassifierImpl
is the parameter to the
FileClassifierProxy
constructor, it is not this class
file that is moved around. It continues to exist on the server
machine. Rather, a stub file is moved around,
and will run on the client machine. This stub is responsible for
sending the method requests back to the implementation class
on the server. This stub has to be generated from the implementation
class by the stub compiler rmic
:
rmic -v1.2 -d /home/webdocs/classes option3.FileClassifierImpl
where the -v1.2
option says to generate JDK 1.2 stubs
only, and the -d
option says where to place the resultant
stub class files so that they can be located by the HTTP server
(in this case, in the local file system).
Note that the pathnames for directories here and later do not include
the package name of the class files. The class files (here
FileClassifierImpl_Stub.class
) will be placed/looked
for in the appropriate subdirectories.
The value of java.rmi.server.codebase
must specify the
protocol used by the HTTP server to find the class files. This could
be the file
protocol or the http
protocol.
If the class files are
stored on my Web server's pages under
classes/option3/FileClassifierImpl_Stub.class
.
the codebase would be specified as
java.rmi.server.codebase=http://myWebHost/classes/
(where myWebHost
is the name of the HTTP's server host).
The server also sets a security manager. This is a restrictive one,
so it needs to be told to allow access. This can be done by setting
the property java.security.policy
to point to a
security policy file such as policy.all
.
Combining all these points leads to startups such as
java -Djava.rmi.server.codebase=http://myWebHost/classes/ \
-Djava.security.policy=policy.all \
FileClassifierServer
The material of the previous chapters is put together in a couple of examples. The requirements of class structures for a Jini system are discussed. Two options, of uploading a complete service and of uploading a proxy only are given. A discussion is also given of what classes need to be available to each component of a Jini system.