Some Simple Examples

Contents

  1. Problem Description
  2. Service Design Options
  3. Common Classes
    1. FileClassifier interface
    2. MIMEType
  4. Client
    1. Unicast Client
    2. Multicast Client
  5. Option 2: Uploading a Complete Service
    1. FileClassifier implementation
    2. Server
    3. Client
    4. What classes need to be where?
    5. Running the option2 FileClassifier
  6. Option 3: Uploading a Proxy
    1. FileClassifier
    2. What Doesn't Change
    3. RemoteFileClassifier
    4. FileClassifierImpl
    5. Server
    6. Staying alive
    7. Proxy
    8. What classes need to be where?
    9. Running the option3 FileClassifier
  7. Summary
This chapter looks at a simple problem, implementing it in a number of different ways

1. Problem Description

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

2. Service 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.

3. Common Classes

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.

3.1 MIMEType

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:

  1. If the service runs in the client JVM, then nothing special needs to be done
  2. If the service runs in a separate JVM, then the MIMEType object must be serialized for transport to the client JVM. For this to be possible, it must implement the Serializable interface
There is a difference in the object depending on possible implementations. If it implements 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




3.2 FileClassifier interface

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!

4. Client

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.

4.1 Unicast Client

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

It has objects that implement the interfaces 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
This shows that the client gets its 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.

4.2 Multicast Client

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

5. Option 2: Uploading a Complete Service

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.

5.1 FileClassifier implementation

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

5.2 Server

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

This receives an object implementing ServiceRegistrar from the service locator (such as reggie). If we add in the service locator and the client in their JVM's, it becomes
The unknown FileClassifier object in the client is here supplied by the service object FileClassifierImpl.

5.3 Client

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.

5.4 What classes need to be where?

We have the classes

  1. common.MIMEType
  2. common.FileClassifier
  3. option2.FileClassifierImpl
  4. option2.FileClassifierServer
  5. client.TestFileClassifier
These could be running on upto four different machines
  1. The server machine for FileClassifier
  2. The machine for the lookup service
  3. The machine running the client TestFileClassifier
  4. An HTTP server will need to run somewhere to deliver the class file definition of FileClassifierImpl to clients
What classes need to be known to which machines?

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

  1. The common.FileClassifier interface
  2. The class common.MIMEType
  3. The class option2.FileClassifierServer
  4. The class option2.FileClassifierImpl
These classes all need to be in the 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

  1. The common.FileClassifier interface
  2. The class common.MIMEType
  3. The class client.TestFileClassifier

5.5 Running the option2 FileClassifier

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

6. Option 3: Uploading a Proxy

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.

6.1 FileClassifier

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

6.2 What Doesn't Change

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.

6.3 RemoteFileClassifier

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

6.4 FileClassifierImpl

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.

6.5 Server

The server has a more complex task. It has to perform the following steps

  1. Create a FileClassifierImpl. (This is automatically exported to the RMI runtime, as it extends UnicastRemoteObject.)
  2. Create a FileClassifierProxy with knowledge of the FileClassifierImpl, passed in as a parameter to the constructor
  3. Export this FileClassifierProxy object to the location service
As a result of this, the proxy will have been exported up to the service locators. As part of its state, it will contain an implementation object, which we passed in as a 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

6.6 Staying alive

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.

6.7 Proxy

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

6.8 What classes need to be where?

We have the classes

  1. common.MIMEType
  2. common.FileClassifier
  3. option3.RemoteFileClassifier
  4. option3.FileClassifierImpl
  5. option3.FileClassifierImpl_Stub
  6. option3.FileClassifierProxy
  7. option3.FileClassifierServer
  8. client.TestFileClassifier
(The class FileClassifierImpl_Stub is added to our classes by rmic as discussed in the next section.) These could be running on upto four different machines
  1. The server machine for FileClassifier
  2. The HTTP server, which may be on a different machine
  3. The machine for the lookup service
  4. 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 common.FileClassifier interface
  2. The option3.RemoteFileClassifier interface
  3. The class common.MIMEType
  4. The class option3.FileClassifierServer
  5. The class option3.FileClassifierImpl
  6. The class 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

  1. The common.FileClassifier interface
  2. The class common.MIMEType

In addition, the HTTP server needs to be able to load and store classes. It needs to be able to access

  1. The option3.FileClassifierImpl_Stub interface
  2. The option3.RemoteFileClassifier interface
  3. The common.FileClassifier interface
  4. The class common.MIMEType
The reason for all of these is slightly complex. In the 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

6.9 Running the option3 FileClassifier

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

7. Summary

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 (©) 1999 by Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name.

This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v0.4 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.