A tutorial on Jini

Jan Newmarch
jan@ise.canberra.edu.au
University of Canberra
DSTC (Distributed Systems Technology CRC)

PROXY IS THE SERVICE


Service implementation


package option2;

import common.MIMEType;
import common.FileClassifier;

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

File classifier server


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;

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


All the JVM's


JOIN MANAGER


Join Manager


Service using Join Manager


package joinmgr;

import option2.FileClassifierImpl;

import com.sun.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import com.sun.jini.lookup.ServiceIDListener;
import com.sun.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscovery;

public class FileClassifierServer implements ServiceIDListener {
    
    public static void main(String argv[]) {
	new FileClassifierServer();

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

    }

    public FileClassifierServer() {

	JoinManager joinMgr = null;
	try {
	    joinMgr = new JoinManager(new FileClassifierImpl(),
				      null,
				      LookupDiscovery.ALL_GROUPS,
				      null,
				      this,
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    public void serviceIDNotify(ServiceID serviceID) {
	System.out.println("got service ID " + serviceID.toString());
    }
    
} // FileClassifierServer

SECURITY


JDK 1.2 Security


Service requirements


grant {
    permission net.jini.discovery.DiscoveryPermission "*";
    // multicast request address
    permission java.net.SocketPermission "224.0.1.85", "connect,accept";
    // multicast announcement address
    permission java.net.SocketPermission "224.0.1.84", "connect,accept";

    // RMI connections
    permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
    permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";

    // reading parameters
    // like net.jini.discovery.debug!
    permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};

Client requirements

The client is most at risk as it imports remote objects into its address space

Client policy


grant {
    permission net.jini.discovery.DiscoveryPermission "*";

    // multicast request address
    permission java.net.SocketPermission "224.0.1.85", "connect,accept";
    // multicast announcement address
    permission java.net.SocketPermission "224.0.1.84", "connect,accept";

    // RMI connections
    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
    permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
    permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";

    // DANGER
    // HTTP connections - this is where external code may come in - careful!!!
    permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
    permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";

    // reading parameters
    // like net.jini.discovery.debug!
    permission java.util.PropertyPermission "net.jini.discovery.*", "read";

};

REMOTE EVENTS


Event models


Remote events


A single listener list

A list with only one listener on it can be done by


protected RemoteEventListener listener = null;

public EventRegistration addRemoteListener(RemoteEventListener listener)
       throws java.util.TooManyListenersException {
    if (this.listener == null {
        this.listener = listener;
    } else {
        throw new java.util.TooManyListenersException();
    }
    return new EventRegistration(0L, this, null, 0L);
}

Single listener event notification

The source object can send an event to its listener by


protected void fireNotify(long eventID,
                          long seqNum) {
    if (listener == null) {
        return;
    }

    RemoteEvent remoteEvent = new RemoteEvent(this, eventID, 
                                              seqNum, null);
    listener.notify(remoteEvent);
}

Mutable file classifier

Allow the file classifier to change its list of MIME type mappings, and notify listeners when it does so


package mutable;

import java.rmi.server.UnicastRemoteObject;
import java.rmi.MarshalledObject;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.EventRegistration;
import java.rmi.RemoteException;
import net.jini.core.event.UnknownEventException ;

import javax.swing.event.EventListenerList;

import common.MIMEType;
import common.MutableFileClassifier;
import java.util.Map;
import java.util.HashMap;

public class FileClassifierImpl extends  UnicastRemoteObject 
                                implements RemoteFileClassifier {

    /**
     * Map of String extensions to MIME types
     */
    protected Map map = new HashMap();

    /**
     * Listeners for change events
     */
    protected EventListenerList listenerList = new EventListenerList();

    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {

	MIMEType type;
	String fileExtension;
	int dotIndex = fileName.lastIndexOf('.');

	if (dotIndex == -1 || dotIndex + 1 == fileName.length()) {
	    // can't find suitable suffix
	    return null;
	}

	fileExtension= fileName.substring(dotIndex + 1);
	type = (MIMEType) map.get(fileExtension);
	return type; 
    }

    public void addType(String suffix, MIMEType type)
	throws java.rmi.RemoteException {
	map.put(suffix, type);
	fireNotify(ADD_TYPE);
    }

    public void removeMIMEType(String suffix, MIMEType type)
	throws java.rmi.RemoteException {
	if (map.remove(suffix) != null) {
	    fireNotify(REMOVE_TYPE);
	}
    }

    public EventRegistration addRemoteListener(RemoteEventListener listener)
	throws java.rmi.RemoteException {
	listenerList.add(RemoteEventListener.class, listener);

	return new EventRegistration(0, this, null, 0);
    }

    // Notify all listeners that have registered interest for
    // notification on this event type.  The event instance 
    // is lazily created using the parameters passed into 
    // the fire method.

    protected void fireNotify(long eventID) {
	RemoteEvent remoteEvent = null;
	
	// Guaranteed to return a non-null array
	Object[] listeners = listenerList.getListenerList();
	
	// Process the listeners last to first, notifying
	// those that are interested in this event
	for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    if (listeners[i] == RemoteEventListener.class) {
		RemoteEventListener listener = (RemoteEventListener) listeners[i+1];
		if (remoteEvent == null) {
		    remoteEvent = new RemoteEvent(this, eventID, 
						  0L, null);
		}
		try {
		    listener.notify(remoteEvent);
		} catch(UnknownEventException e) {
		    e.printStackTrace();
		} catch(RemoteException e) {
		    e.printStackTrace();
		}
	    }
	}
    }    

    public FileClassifierImpl()  throws java.rmi.RemoteException {
	// load a predefined set of MIME type mappings
	map.put("gif", new MIMEType("image", "gif"));
	map.put("jpeg", new MIMEType("image", "jpeg"));
	map.put("mpg", new MIMEType("video", "mpeg"));
	map.put("txt", new MIMEType("text", "plain"));
	map.put("html", new MIMEType("text", "html"));
    }
} // FileClassifierImpl

INTERFACING TO HARDWARE


Lego MindStorms

MindStorms is a ``Robotics Invention System''

RCX programmed from Java


MindStorms as a Jini Service


RCX Port Impl

This is best done as an RMI proxy to the service on the server. The service has an RCXPort as attribute, and passes on messages to it.


package rcx.jini;

import java.rmi.server.UnicastRemoteObject;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import rcx.*;
import java.io.*;
import java.util.*;

public class RCXPortImpl extends UnicastRemoteObject
    implements RemoteRCXPort, RCXListener {

    protected String error = null;
    protected byte[] message = null;
    protected RCXPort port = null;
    protected RemoteEventListener listener = null;
    protected long messageSeqNo, errorSeqNo;  

    public RCXPortImpl() 
	throws java.rmi.RemoteException {

	Properties parameters;
	String portName = null;
	File f = new File("parameters.txt");
        if (!f.exists()) {
            f = new File(System.getProperty("user.dir")
                         + System.getProperty("path.separator")
                         + "parameters.txt");
        }
        if (f.exists()) {
            try {
		FileInputStream fis = new FileInputStream(f);
		parameters = new Properties();
		parameters.load(fis);
		fis.close();
                portName = parameters.getProperty("port");
            } catch (IOException e) { }
        } else {
	    System.err.println("Can't find parameters.txt with \"port=...\" specified");
	    System.exit(1);
	}
	
        port = new RCXPort(portName);
        port.addRCXListener(this);
	
    }
    
    public boolean write(byte[] byteCommands)
	throws java.rmi.RemoteException {
	return port.write(byteCommands);
    }

    public byte[] parseString(String command) 
	throws java.rmi.RemoteException {
	return RCXPort.parseString(command);
    }

    /**
     * Received a message from the RCX.
     * Send it to the listener
     */
    public void receivedMessage(byte[] message) {

	this.message = message;

	// Send it out to listener
	if (listener == null) {
	    return;
	}

	RemoteEvent evt = new RemoteEvent(this, MESSAGE_EVENT, messageSeqNo++, null);
	try {
	    listener.notify(evt); 
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Received an error message from the RCX.
     * Send it to the listener
     */
    public void receivedError(String error) {
	// System.err.println(error);

	// Send it out to listener
	if (listener == null) {
	    return;
	}
	this.error = error;
	RemoteEvent evt = new RemoteEvent(this, ERROR_EVENT, errorSeqNo, null);
	try {
	    listener.notify(evt);
	} catch(net.jini.core.event.UnknownEventException e) {
	    e.printStackTrace();
	} catch(java.rmi.RemoteException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Expected use: the RCX has returned a message, 
     * and we have informed the listeners. They query
     * this method to find the message for the message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public byte[] getMessage(long msgSeqNo) {
	return message;
    }

    /**
     * Expected use: the RCX has returned an error message, 
     * and we have informed the listeners. They query
     * this method to find the error message for the error message
     * seqence number they were given in the RemoteEvent.
     * We could use this as an index into a table of messages.
     */
    public String getError(long errSeqNo) {
	return error;
    }

    /**
     * Add a listener for RCX messages.
     * Should allow more than one, or throw
     * TooManyListeners if more than one registers
     */
    public void addListener(RemoteEventListener listener) {
	this.listener = listener;
	messageSeqNo = 0;
	errorSeqNo = 0;
    }
} // RCXPortImpl

Future MindStorms Work


CORBA AND JINI


CORBA

CORBA is also an infrastructure for distributed systems. It is targeted at a slightly different ``space'' to Jini, and has some minor and major differences.

CORBA Hello World


Jini/CORBA interaction


Jini Hello service


package corba;

import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import corba.HelloApp.*;

public class JavaHelloImpl implements JavaHello {
    
    protected Hello helloRef = null;
    protected String[] argv;

    public JavaHelloImpl(String[] argv) {
	this.argv = argv;
    }

    public String sayHello() {
	// Hello helloRef = null;

	if (helloRef == null) {
	    helloRef = getHelloRef();
	}
	// now invoke methods on the CORBA proxy
	String hello = helloRef.sayHello();
	return hello;
    }

    protected Hello getHelloRef() {
	ORB orb = null;
	// Act like a CORBA client
	try {
	    orb = ORB.init(argv, null);

	    // find the CORBA name server
	    org.omg.CORBA.Object objRef = 
		orb.resolve_initial_references("NameService");
	    NamingContext ncRef = NamingContextHelper.narrow(objRef);

 	    // find the CORBA Hello proxy
	    NameComponent nc = new NameComponent("Hello", "");
	    NameComponent path[] = {nc};
	    org.omg.CORBA.Object obj = ncRef.resolve(path);
	    Hello helloRef = HelloHelper.narrow(obj);
	    return helloRef;
	} catch(Exception e) {
	    e.printStackTrace();
	    return null;
	}
    }
} // JavaHelloImpl

Future Work


CONCLUSION


Problems and Issues


Conclusion


URLs


Jan Newmarch (http://pandonia.canberra.edu.au)
jan@ise.canberra.edu.au
Last modified: Sat Sep 4 00:14:09 EST 1999
Copyright ©Jan Newmarch