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?
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.
The client will often need to search through all
of the service locators till it finds one holding a service it
is looking for. If it only needs one, then it can do something like
exit after using the service. More complex behaviour will be dealt
with later (when I find a good example???).
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(10000L);
} 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
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
.
In the form given above, the FileClassifier
is basically
just a lookup table, with one static method. There is no need to
create instances of this type, and this could even have been eliminated
as a possibility by declaring the constructor as private
:
private FileClassifier() {
}
If we want to make this into a service that will be exported, we have to make a couple of changes to this.
Firstly, we will need to separate
out an interface class FileClassifier
and at least one
implementation class which we shall here give as
FileClassifierImpl
. Now interfaces cannot have
static
methods, so we shall have to turn the method into
a public instance method. In addition, a class object for this
interface, and later instances implementing this will need to be
shipped around, so it should extend Serializable
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 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 class MIMEType
does not need to be moved around, so
its definition remains unaltered
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
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 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 are based on the
timeservice
code from Eran Davidov at
www.artima.com/jini/resources/timeservice.html
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.
There will already be an RMI daemon running on the service machine
since it has to receive Registrar
's back from the
lookup locator.
In order to register FileClassifierImpl
for access
by RMI, an rmiregistry
will need to be set running
on the service machines.
In order for FileClassifierImpl
to be accessed from there,
the class must
implement the RMI Remote
interface. In addition
the FileClassifierProxy
must be aware of this service
in the RMI registry so that it can invoke it. This must all be
invisible to the client, which just wants to know about
FileClassifier
. For cleanness, since there may be
many implementations of the service, and they all have to
implement Remote
, make an intermediate interface
RemoteFileClassifier
. 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 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;
/**
* 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 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
The server has a more complex task. It has to perform the following steps
FileClassifierImpl
FileClassifierImpl
FileClassifierProxy
with knowledge of
the registration name of the FileClassifierImpl
FileClassifierProxy
object to the
location service
package option3;
import java.rmi.Naming;
import java.net.InetAddress;
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.1
* added LeaseRenewalManager
* moved sleep() from constructor to main()
*/
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);
}
// register this with RMI registry
System.setSecurityManager(new RMISecurityManager());
/*
try {
Naming.rebind("rmi://localhost/" + serviceName, impl);
} catch(java.net.MalformedURLException e) {
System.err.println("Binding: " + e.toString());
System.exit(1);
} catch(java.rmi.RemoteException e) {
System.err.println("Binding: " + e.toString());
System.exit(1);
}
System.out.println("bound");
// find where we are running
String address = null;
try {
address = InetAddress.getLocalHost().getHostName();
} catch(java.net.UnknownHostException e) {
System.err.println("Address: " + e.toString());
System.exit(1);
}
String registeredName = "//" + address + "/" + serviceName;
*/
// make a proxy that knows the service address
// proxy = new FileClassifierProxy(registeredName);
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++) {
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 using the proxy's
readObject
. At this point we attempt to find the
real service.
The real service is an instance of FileClassifierImpl
running on the server machine. What we have in the proxy is a
serviceLocation
for this object which will be of
the form ``//hostname/FileClassifier''. This is actually an RMI
reference, which we can retrieve using the
RMI call Naming.lookup()
. This will return an object
to the FileClassifierProxy
which in fact is
another proxy - the RMI proxy for the FileClassifierImpl
!
This RMI proxy (or stub) is of type FileClassifierImpl_Stub
and is generated by rmic
, as described later.
This double layer of proxies 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.0
*/
public class FileClassifierProxy implements FileClassifier, Serializable {
protected String serviceLocation;
/*transient*/ RemoteFileClassifier server = null;
/*
public FileClassifierProxy(String serviceLocation) {
this.serviceLocation = serviceLocation;
}
*/
public FileClassifierProxy(FileClassifierImpl serv) {
this.server = serv;
if (serv==null) System.err.println("server is null");
}
/*
private void readObject(java.io.ObjectInputStream stream)
throws java.io.IOException, ClassNotFoundException {
stream.defaultReadObject();
try {
Object obj = Naming.lookup(serviceLocation);
server = (RemoteFileClassifier) obj;
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
}
*/
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
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
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 three different machines
FileClassifier
which must also be running the rmiregistry
and the HTTP server
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 rmiregistry
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
Naming.rebind()
call, 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 RMI registry will look for files based on the
java.rmi.server.codebase
property of the application using the registry. The value of this
property is a URL, which can be a file or http reference.
If java.rmi.server.codebase
is
a file reference, then the above class files must be accessible from
that reference.
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://hostname/classes
(where hostname
is the name of the server's host)
and the 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
In addition to loading files into the rmiregistry
,
this application is also loading FileClassifierProxy
up to the lookup locator and on to clients. This has similar requirements
to option 2, and needs these files to be on the http server:
/home/webdocs/classes/option3/FileClassifierProxy.class
/home/webdocs/classes/option3/RemoteFileClassifier.class
(There is some duplication here with the files needed for
rmiregistry
.)
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 it is making RMI calls such as
Binding.rebind()
, and these have additional requirements.
Firstly, RMI stubs must be generated during compilation.
Secondly, an rmiregistry
must be running on the server
machine so that the name ``FileClassifier'' can be bound in it,
and thirdly security rights must be set since an
RMISecurityManager
is used.
Although the FileClassifierImpl
is bound to an RMI
registry by Naming.rebind()
, 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 an rmiregistry
.
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 registry will need to be able to locate the stub files. Typically
this is done by setting the property java.rmi.server.codebase
when running the server. This will then communicate to the registry where
to look. The easiest way to set a property is by the -D
option to the Java runtime engine. Note that the registry
must not be able to locate the stub files
through its CLASSPATH
setting, or confusion results.
This can be done by running the registry in a different directory
or by unsetting the environment variable.
The value of java.rmi.server.codebase
must specify the
protocol used by the registry to find the class files. This could
be the file
protocol or the http
protocol.
But we also need an HTTP server running for the proxy code, so we
can't use the file 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://hostname/classes/
(where hostname
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
(unset CLASSPATH; rmiregistry) &
java -Djava.rmi.server.codebase=http:/localhost/classes/ \
-Djava.security.policy=policy.all \
FileClassifierServer
The actual movement of files around the network is described in
the figure
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.
This file is Copyright ©Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name