Some Simple Examples

This chapter looks at a simple problem, implementing it in a number of different ways

Contents

  1. Problem Description
  2. Client
  3. Server Design Options
  4. Option 2: Uploading a Complete Service
    1. FileClassifier interface
    2. FileClassifier implementation
    3. MIMEType
    4. Server
    5. Client
    6. What classes need to be where?
    7. Running the option2 FileClassifier
  5. Option 3: Uploading a Proxy
    1. FileClassifier
    2. RemoteFileClassifier
    3. FileClassifierImpl
    4. MIMEType
    5. Server
    6. Staying alive
    7. Proxy
    8. Client
    9. What classes need to be where?
    10. Running the option3 FileClassifier
  6. Summary

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

The client will often need to search through all of the service locators till it finds one holding a service it is looking for. If it only needs one, then it can do something like exit after using the service. More complex behaviour will be dealt with later (when I find a good example???). The client does not need to have long-term persistence. But it does need a user thread to remain in existence for long enough to find service locators and find a suitable service. So in main() a user thread again sleeps for a short period (ten seconds).



package client;

import common.FileClassifier;
import common.MIMEType;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;


/**
 * TestFileClassifier.java
 *
 *
 * Created: Wed Mar 17 14:29:15 1999
 *
 * @author Jan Newmarch
 * @version 1.3
 *    moved sleep() from constructor to main()
 *    moved to package client
 *    simplified Class.forName to Class.class
 */

public class TestFileClassifier implements DiscoveryListener {

    public static void main(String argv[]) {
	new TestFileClassifier();

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(10000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    public TestFileClassifier() {
	System.setSecurityManager(new RMISecurityManager());

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);

    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	Class [] classes = new Class[] {FileClassifier.class};
	FileClassifier classifier = null;
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);
 
        for (int n = 0; n < registrars.length; n++) {
	    System.out.println("Service found");
            ServiceRegistrar registrar = registrars[n];
	    try {
		classifier = (FileClassifier) registrar.lookup(template);
	    } catch(java.rmi.RemoteException e) {
		e.printStackTrace();
		System.exit(2);
	    }
	    if (classifier == null) {
		System.out.println("Classifier null");
		continue;
	    }
	    MIMEType type;
	    try {
		type = classifier.getMIMEType("file1.txt");
		System.out.println("Type is " + type.toString());
	    } catch(java.rmi.RemoteException e) {
		System.err.println(e.toString());
	    }
	    // System.exit(0);
	}
    }

    public void discarded(DiscoveryEvent evt) {
	// empty
    }
} // TestFileClassifier

3. Server 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.

4. 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.

4.1 FileClassifier interface

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!

4.2 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

4.3 MIMEType

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




4.4 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

4.5 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.

4.6 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

4.7 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

5. 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 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.

5.1 FileClassifier

There will already be an RMI daemon running on the service machine since it has to receive Registrar's back from the lookup locator. In order to register FileClassifierImpl for access by RMI, an rmiregistry will need to be set running on the service machines. In order for FileClassifierImpl to be accessed from there, the class must implement the RMI Remote interface. In addition the FileClassifierProxy must be aware of this service in the RMI registry so that it can invoke it. This must all be invisible to the client, which just wants to know about FileClassifier. For cleanness, since there may be many implementations of the service, and they all have to implement Remote, make an intermediate interface RemoteFileClassifier. This gives a class diagram of



The FileClassifier interface remains unchanged as



package common;

import java.io.Serializable;

/**
 * FileClassifier.java
 *
 *
 * Created: Wed Mar 17 14:14:36 1999
 *
 * @author Jan Newmarch
 * @version 1.1
 *    moved to package common
 */

public interface FileClassifier extends Serializable {
    
    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException;
    
} // FileClasssifier

5.2 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

5.3 FileClassifierImpl

The class FileClassifierImpl just changes its inheritance:



package option3;

import java.rmi.server.UnicastRemoteObject;
import common.MIMEType;

/**
 * FileClassifierImpl.java
 *
 *
 * Created: Wed Mar 17 14:22:13 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

public class FileClassifierImpl extends  UnicastRemoteObject implements RemoteFileClassifier {
    
    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
	System.out.println("Called with " + fileName);
        if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else
            // fill in lots of other types,
            // but eventually give up and
            return new MIMEType(null, null);
    }


    public FileClassifierImpl()  throws java.rmi.RemoteException {
	// empty
    }
    
} // FileClassifierImpl

The FileClassifierProxy is a little more complex and will be discussed soon.

5.4 MIMEType

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




5.5 Server

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 FileClassifierProxy with knowledge of the registration name of the FileClassifierImpl
  5. Export this FileClassifierProxy object to the location service
This is done by


package option3;

import java.rmi.Naming;
import java.net.InetAddress;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import com.sun.jini.lease.LeaseRenewalManager;
import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;
import java.rmi.RMISecurityManager;

/**
 * FileClassifierServer.java
 *
 *
 * Created: Wed Mar 17 14:23:44 1999
 *
 * @author Jan Newmarch
 * @version 1.1
 *    added LeaseRenewalManager
 *    moved sleep() from constructor to main()
 */

public class FileClassifierServer implements DiscoveryListener, LeaseListener {

    // this is just a name - can be anything
    // impl object forces search for Stub
    static final String serviceName = "FileClassifier";

    protected FileClassifierImpl impl;
    protected FileClassifierProxy proxy;
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    
    public static void main(String argv[]) {
	new FileClassifierServer();

        // no need to keep server alive, RMI will do that
    }

    public FileClassifierServer() {
	try {
	    impl = new FileClassifierImpl();
	} catch(Exception e) {
            System.err.println("New impl: " + e.toString());
            System.exit(1);
	}

	// register this with RMI registry
	System.setSecurityManager(new RMISecurityManager());
	/*
	try {
	    Naming.rebind("rmi://localhost/" + serviceName, impl);
	} catch(java.net.MalformedURLException e) {
            System.err.println("Binding: " + e.toString());
            System.exit(1);
	} catch(java.rmi.RemoteException e) {
            System.err.println("Binding: " + e.toString());
            System.exit(1);
        }

	System.out.println("bound");
	// find where we are running
	String address = null;
	try {
	    address = InetAddress.getLocalHost().getHostName();
	} catch(java.net.UnknownHostException e) {
            System.err.println("Address: " + e.toString());
            System.exit(1);
        }
	
	String registeredName = "//" + address + "/" + serviceName;
	*/

	// make a proxy that knows the service address
	// proxy = new FileClassifierProxy(registeredName);
	proxy = new FileClassifierProxy(impl);

	// now continue as before
	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);
    }
    
    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();

        for (int n = 0; n < registrars.length; n++) {
            ServiceRegistrar registrar = registrars[n];

	    // export the proxy service
	    ServiceItem item = new ServiceItem(null,
					       proxy, 
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.print("Register exception: ");
		e.printStackTrace();
		// System.exit(2);
		continue;
	    }
	    try {
		System.out.println("service registered at " +
				   registrar.getLocator().getHost());
	    } catch(Exception e) {
	    }
	    leaseManager.renewFor(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }
        
} // FileClassifierServer

5.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.

5.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 using the proxy's readObject. At this point we attempt to find the real service.

The real service is an instance of FileClassifierImpl running on the server machine. What we have in the proxy is a serviceLocation for this object which will be of the form ``//hostname/FileClassifier''. This is actually an RMI reference, which we can retrieve using the RMI call Naming.lookup(). This will return an object to the FileClassifierProxy which in fact is another proxy - the RMI proxy for the FileClassifierImpl! This RMI proxy (or stub) is of type FileClassifierImpl_Stub and is generated by rmic, as described later. This double layer of proxies is caused by the use of RMI to access the FileClassifierImpl.

Later, when the client calls getMIMEType() on the FileClassifierProxy it will invoke the same method on the RMI proxy stub which will send a remote call to the real server to do this:



package option3;

import common.FileClassifier;
import common.MIMEType;

import java.io.Serializable;
import java.io.IOException;
import java.rmi.Naming;

/**
 * FileClassifierProxy
 *
 *
 * Created: Thu Mar 18 14:32:32 1999
 *
 * @author Jan Newmarch
 * @version 1.0
 */

public class FileClassifierProxy implements FileClassifier, Serializable {

    protected String serviceLocation;
    /*transient*/ RemoteFileClassifier server = null;

    /*
    public FileClassifierProxy(String serviceLocation) {
	this.serviceLocation = serviceLocation;
    }
    */
    public FileClassifierProxy(FileClassifierImpl serv) {
	this.server = serv;
if (serv==null) System.err.println("server is null");
    }

    /*
    private void readObject(java.io.ObjectInputStream stream) 
	throws java.io.IOException, ClassNotFoundException {
	stream.defaultReadObject();

	try {
	    Object obj = Naming.lookup(serviceLocation);
	    server = (RemoteFileClassifier) obj;
	} catch(Exception e) {
	    System.err.println(e.toString());
	    System.exit(1);
	}
    }
    */

    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
if (server==null) System.err.println("server2 is null");
if (fileName==null) System.err.println("filename is null");
	return server.getMIMEType(fileName);
    }
} // FileClassifierProxy

5.8 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.9 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 three different machines
  1. The server machine for FileClassifier which must also be running the rmiregistry and the HTTP server
  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 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 rmiregistry 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 Naming.rebind() call, the class FileClassifierImpl is passed in. The RMI runtime converts this to FileClassifierImpl_Stub. This class implements the same interfaces as FileClassifierImpl: that is, RemoteFileClassifier and hence FileClassifier, so these also need to be available. In the implementation, FileClassifierImpl references the class MIMEType, so this must also be available.

What does the phrase ``available'' mean in the last paragraph? The RMI registry will look for files based on the java.rmi.server.codebase property of the application using the registry. The value of this property is a URL, which can be a file or http reference.

If java.rmi.server.codebase is a file reference, then the above class files must be accessible from that reference. If java.rmi.server.codebase is an http reference, then the above class files must be accessible from that reference. For example, suppose the property is set to


java.rmi.server.codebase=http://hostname/classes
(where hostname is the name of the server's host) and the Web server has its DocumentRoot set to /home/webdocs then these files must exist

/home/webdocs/classes/option3/FileClassifierImpl_Stub.class
/home/webdocs/classes/option3/RemoteFileClassifier.class
/home/webdocs/classes/common/FileClassifier.class
/home/webdocs/classes/common/MIMEType.class

In addition to loading files into the rmiregistry, this application is also loading FileClassifierProxy up to the lookup locator and on to clients. This has similar requirements to option 2, and needs these files to be on the http server:


/home/webdocs/classes/option3/FileClassifierProxy.class
/home/webdocs/classes/option3/RemoteFileClassifier.class
(There is some duplication here with the files needed for rmiregistry.)

5.10 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 it is making RMI calls such as Binding.rebind(), and these have additional requirements. Firstly, RMI stubs must be generated during compilation. Secondly, an rmiregistry must be running on the server machine so that the name ``FileClassifier'' can be bound in it, and thirdly security rights must be set since an RMISecurityManager is used.

Although the FileClassifierImpl is bound to an RMI registry by Naming.rebind(), it is not this class file that is moved around. It continues to exist on the server machine. Rather, a stub file is moved around, and will run on the client machine. This stub is responsible for sending the method requests back to the implementation class on the server. This stub has to be generated from the implementation class by the stub compiler rmic:


rmic -v1.2 -d /home/webdocs/classes option3.FileClassifierImpl
where the -v1.2 option says to generate JDK 1.2 stubs only, and the -d option says where to place the resultant stub class files so that they can be located by an rmiregistry. Note that the pathnames for directories here and later do not include the package name of the class files. The class files (here FileClassifierImpl_Stub.class) will be placed/looked for in the appropriate subdirectories.

The registry will need to be able to locate the stub files. Typically this is done by setting the property java.rmi.server.codebase when running the server. This will then communicate to the registry where to look. The easiest way to set a property is by the -D option to the Java runtime engine. Note that the registry must not be able to locate the stub files through its CLASSPATH setting, or confusion results. This can be done by running the registry in a different directory or by unsetting the environment variable.

The value of java.rmi.server.codebase must specify the protocol used by the registry to find the class files. This could be the file protocol or the http protocol. But we also need an HTTP server running for the proxy code, so we can't use the file protocol. If the class files are stored on my Web server's pages under classes/option3/FileClassifierImpl_Stub.class. the codebase would be specified as


java.rmi.server.codebase=http://hostname/classes/
(where hostname is the name of the HTTP's server host).

The server also sets a security manager. This is a restrictive one, so it needs to be told to allow access. This can be done by setting the property java.security.policy to point to a security policy file such as policy.all.

Combining all these points leads to startups such as


(unset CLASSPATH; rmiregistry) &
java -Djava.rmi.server.codebase=http:/localhost/classes/ \
     -Djava.security.policy=policy.all \
     FileClassifierServer

The actual movement of files around the network is described in the figure



6. 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 ©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.