Some Simple Examples

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:

  1. By examining the file's name
  2. By examing the file's contents
Utilities such as the Unix 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?

1. Design Options

If we wish to make a version of FileClassifier available across the network, there are a number of options

Option 1
The silly one: push the entire implementation upto the lookup service and make the client ask for it by its class. Then the client might just as well create the classifier as a local object as it has all the information needed!
Option 2
Separate the interface from the implementation. Make the interface available to the client, and upload the implementation to the lookup service. Then when the client asks for an instance object that implements the interface, it will get the classifier object. This will reduce maintenance: if the client is coded just in terms of the interface then it will not need recompilation even if the implementation changes. Note that these words will translate straight into Java terms: the client knows about a Java interface, whereas the server deals in terms of a Java class that implement's the interface.
Option 3
It is possible that a classifier running as a service might change over time, For example, it may have a means of listening for new MIME-type announcements made by other applications. If it uploads a complete copy of itself to the lookup service, then that copy might become ``stale'' with respect to the ``real'' service, since it is just a serialized ``snapshot'' at the instant of time it was uploaded. An alternative is to upload a proxy that will refer back to the ``live'' service when needed. This will be even more important for services which are in control of volatile pieces of hardware, such as the proverbial toaster.

2. Option 2: Uploading a Complete Service

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???).

2.1 What needs to be where?

We have the classes

  1. MIMEType
  2. FileClassifier
  3. FileClassifierImpl
  4. FileClassifierServer
  5. TestFileClassifier
These could be running on upto three different machines
  1. The server machine for FileClassifier
  2. The machine for the lookup service
  3. The machine running the client TestFileClassifier
What classes need to be known to which machines?

The server running FileClassifierServer needs to know the following classes and interfaces

  1. The FileClassifier interface
  2. The class MIMEType
  3. The class FileClassifierServer
  4. The class 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

  1. The FileClassifier interface
  2. The class MIMEType

3. Option 3: Uploading a Proxy

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

  1. Create a FileClassifierImpl
  2. Register this with the local RMI registry
  3. Find the hostname of this registry, and hence the registration name of the FileClassifierImpl
  4. Create a FileClassifierImpl with knowledge of the registration name of the FileClassifierImpl
  5. Export this FileClassifierImpl object to the location service

This file is Copyright ©Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name

The copyright is the OpenContent License (http://www.opencontent.org/opl.shtml), which is the ``document'' version of the GNU OpenSource license.