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?
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(1000000L);
} 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
class MyRMISecurityManager extends RMISecurityManager {
public void checkPermission(java.security.Permission perm) {
super.checkPermission(perm);
}
}
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 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 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 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
.
(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
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 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.