A tutorial on Jini

Jan Newmarch
jan@ise.canberra.edu.au
University of Canberra
Monash University

OVERVIEW


What is Jini?


Jini Federation


Figure 1: Components of a Jini federation

Service Registration


Figure 2: Querying for a service locator

Figure 3: Registrar returned

Figure 4: Service uploaded

Client Lookup


Figure 5: Querying for a service locator

Figure 6: Registrar returned

Figure 7: Asking for a service

Figure 8: Service returned

Support Services


Proxies


Attribute Registration


Service Location


Leasing


Non-distributed Application


Distributed Version


Client Structure

Internally a client will look like

prepare for discovery
discover a lookup service
prepare a template for lookup search
lookup a service
call the service

Server Structure

prepare for discovery
discover a lookup service
create information about a service
export a service
renew leasing periodically


DISCOVERING A LOOKUP SERVICE


Multicast discovery


Multicast discovery program


import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;

public class MulticastRegister implements DiscoveryListener {
 
    static public void main(String argv[]) {
        new MulticastRegister();

	// stay around long enough to receive replies
	try {
	    Thread.currentThread().sleep(10000L);
	} catch(java.lang.InterruptedException e) {
	    // do nothing
	}
    }
      
    public MulticastRegister() {
        LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
	    e.printStackTrace();
	    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];

	    // the code takes separate routes from here for client or service
	    System.out.println("found a service locator");
  	}
    }

    public void discarded(DiscoveryEvent evt) {

    }
} // MulticastRegister

ServiceRegistrar


ENTRIES


Entry class


Creating entries


Interfaces, implementations and entries


CLIENT


Problem domain



Client and service locator JVM's

The two JVM's look like

Multicast client

Use this pattern when you don't know where the lookup locator(s) are:

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;

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


PROXY IMPLEMENTATION CHOICES


Proxy is the Service


RMI Proxies


Non-RMI Proxy


RMI and Non-RMI Proxies


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


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

Realistic Use of Events


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

CONCLUSION


Problems and Issues


Conclusion


URLs


Jan Newmarch (http://pandonia.canberra.edu.au)
jan@ise.canberra.edu.au
Last modified: Mon Oct 16 13:53:55 EST 2000
Copyright ©Jan Newmarch