Some Simple Examples

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
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. 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(1000000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

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

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

        discover.addDiscoveryListener(this);

    }
    
    public void discovered(DiscoveryEvent evt) {

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

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


class MyRMISecurityManager extends RMISecurityManager {

    public void checkPermission(java.security.Permission perm) {
	super.checkPermission(perm);
    }
}

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

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

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

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







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

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

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

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 (©) 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.