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 correspoding
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
/**
* MIMEType.java
*
*
* Created: Mon Mar 15 22:09:13 1999
*
* @author Jan Newmarch
* @version
*/
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
import MIMEType;
/**
* FileClassifier.java
*
*
* Created: Mon Mar 15 22:18:59 1999
*
* @author Jan Newmarch
* @version
*/
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 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.
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 by RMI, so it should extend Serializable
package option1;
import java.io.Serializable;
/**
* FileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version
*/
public interface FileClassifier extends Serializable {
public MIMEType getMIMEType(String fileName);
} // FileClasssifier
The implementation of this is straightforward
package option1;
/**
* FileClassifierImpl.java
*
*
* Created: Wed Mar 17 14:22:13 1999
*
* @author Jan Newmarch
* @version
*/
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 option1;
/**
* MIMEType.java
*
*
* Created: Wed Mar 17 14:17:32 1999
*
* @author Jan Newmarch
* @version
*/
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
The server for this just needs to create an instance of the exportable
service and register this. Note that this server only sticks around
for 10 seconds (10,000 milliseconds) but it requests that the exported
service should last forever (Lease.FOREVER).
If the lease request is granted, then there is no need for the server
to hang around longer than needed to upload the exported service.
This server tries to export the service to all the service locators
that it finds in its 10 seconds of life.
package option1;
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;
/**
* FileClassifierServer.java
*
*
* Created: Wed Mar 17 14:23:44 1999
*
* @author Jan Newmarch
* @version
*/
public class FileClassifierServer implements DiscoveryListener {
public static void main(String argv[]) {
new FileClassifierServer();
}
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);
// stay around long enough to receive replies
try {
Thread.currentThread().sleep(10000L);
} catch(java.lang.InterruptedException e) {
// do nothing
}
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
for (int n = 0; n < registrars.length; n++) {
ServiceRegistrar registrar = registrars[n];
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");
}
}
public void discarded(DiscoveryEvent evt) {
}
} // FileClassifierServer
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???).
We have the classes
MIMEType
FileClassifier
FileClassifierImpl
FileClassifierServer
TestFileClassifier
FileClassifier
TestFileClassifier
The server running FileClassifierServer needs to know the
following classes and interfaces
FileClassifier interface
MIMEType
FileClassifierServer
FileClassifierImpl
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
FileClassifier interface
MIMEType
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 drop down to a lower level, that of RMI. That is, 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. A FileClassifierImpl can register
itself with this. In order to be accessed from there, it 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 option2;
import java.io.Serializable;
/**
* FileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version
*/
public interface FileClassifier extends Serializable {
public MIMEType getMIMEType(String fileName)
throws java.rmi.RemoteException;
} // FileClasssifier
The interface RemoteFileClassifier just adds the
Remote interface
package option2;
import java.rmi.Remote;
/**
* RemoteFileClassifier.java
*
*
* Created: Wed Mar 17 14:14:36 1999
*
* @author Jan Newmarch
* @version
*/
public interface RemoteFileClassifier extends FileClassifier, Remote {
} // RemoteFileClasssifier
The class FileClassifierImpl just changes its
inheritance:
package option2;
import java.rmi.server.UnicastRemoteObject;
/**
* FileClassifierImpl.java
*
*
* Created: Wed Mar 17 14:22:13 1999
*
* @author Jan Newmarch
* @version
*/
public class FileClassifierImpl extends UnicastRemoteObject implements RemoteFileClassifier {
public option2.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 option2;
import java.io.Serializable;
/**
* MIMEType.java
*
*
* Created: Wed Mar 17 14:17:32 1999
*
* @author Jan Newmarch
* @version
*/
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
FileClassifierImpl with knowledge of
the registration name of the FileClassifierImpl
FileClassifierImpl object to the
location service