Even more Jini

JOIN MANAGER


Join Manager


Service using Join Manager


/**
 * FileClassifierServer2.java
 */

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

        // stay around forever
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		keepAlive.wait();
	    } catch(InterruptedException e) {
		// do nothing
	    }
	}
    }

    public FileClassifierServer2() {

	JoinManager joinMgr = null;
	try {
	    LookupDiscoveryManager mgr = 
		new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
					   null, // unicast locators
					   null); // DiscoveryListener
	    joinMgr = new JoinManager(new FileClassifierImpl(), // service
				      null, // attr sets
				      this, // ServiceIDListener
				      mgr, // DiscoveryManagement
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    public void serviceIDNotify(ServiceID serviceID) {
	// called as a ServiceIDListener
	// Should save the id to permanent storage
	System.out.println("got service ID " + serviceID.toString());
    }
    
} // FileClassifierServer


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);
}

A multiple listener list

A list with multiple listeners on it can be done by

protected Vector listeners = new Vector();

public EventRegistration addRemoteListener(RemoteEventListener listener) {
    listeners.add(listener);
    return new EventRegistration(0L, this, null, 0L);
}


Multiple listener event notification

The source object can send an event to its listener list by

protected void fireNotify(long eventID,
                          long seqNum) {
    RemoteEvent remoteEvent = new RemoteEvent(this, eventID, 
                                              seqNum, null);
    for (int n = 0; n < listeners.size(); n++) {
        RemoteListener listener = (RemoteListener) listeners.elementAt(n);
        listener.notify(remoteEvent);
    }
}

Remote events using echo

Echo interface


package service;

import java.rmi.*;

// Change start import net.jini.core.event.*;
// Change end
// Change start import net.jini.core.event.*;
// Change end public interface Echo extends Remote { // There isn't any state with an echo service, which makes it a bit // hard to generate events. Implausibly, instead we have an event type // TEXT_CHANGED, which changes each time echo() is called, and an event // is generated each time a change occurs
// Change start from echo without events static final int TEXT_CHANGED = 1; public EventRegistration addEventListener(RemoteEventListener l) throws RemoteException; /* If we have an event, we must be able to find what changed */ public String getChangedText() throws RemoteException;
// Change end String echo(String in) throws RemoteException; }

Echo interface implementation



package server;

import service.*;

// Change start import net.jini.core.event.*; import java.util.Vector; import java.rmi.RemoteException;
// Change end public class EchoImpl implements Echo {
// Change start private Vector listeners = new Vector(); private int seqNum = 0; // event sequence counter private String text = "";
// Change end public EchoImpl() { } public String echo(String in) {
// Change start fireNotify(TEXT_CHANGED); text = in;
// Change end return "Echo: " + in; }
// Change start public String getChangedText() { return text; }
// Change end
// Change start public EventRegistration addEventListener(RemoteEventListener listener) { listeners.add(listener); return new EventRegistration(0L, this, null, 0L); }
// Change end
// Change start protected void fireNotify(long eventID) { RemoteEvent remoteEvent = new RemoteEvent(this, eventID, seqNum++, null); for (int n = 0; n < listeners.size(); n++) { RemoteEventListener listener = (RemoteEventListener) listeners.elementAt(n); try { listener.notify(remoteEvent); } catch(UnknownEventException e) { // ignore } catch(RemoteException e) { // ignore } } }
// Change end }

Echo server



package server;

import service.*;

import java.io.*;

import java.rmi.*; 
import java.rmi.server.ExportException;
import net.jini.export.*; 
import net.jini.jrmp.JrmpExporter;
import net.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceRegistrar;
import java.rmi.RemoteException;
import net.jini.lookup.ServiceIDListener;
import net.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscoveryManager;

public class EchoServer implements ServiceIDListener {

    public static void main(String[] args) {
	EchoServer svr = new EchoServer();

	// This is a hack to keep the server alive.
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		// Wait for a "notify" from another thread
		// that will never be sent.
		// So we stay alive for ever
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

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

	Exporter exporter = new JrmpExporter();

	Remote echo = new EchoImpl();
	// export an Echo proxy
	Remote proxy = null;
	try {
	    proxy = exporter.export(echo);
	} catch(ExportException e) {
	    System.exit(1);
	}

	// Register with all lookup services as they are discovered
	JoinManager joinMgr = null;
	try {
	    LookupDiscoveryManager mgr = 
		new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
					   null,  // unicast locators
					   null); // DiscoveryListener
	    joinMgr = new JoinManager(proxy, // service proxy
				      null,  // attr sets
				      this,  // ServiceIDListener
				      mgr,   // DiscoveryManager
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    } 

    public void serviceIDNotify(ServiceID serviceID) {
	// called as a ServiceIDListener
	// Should save the id to permanent storage
	System.out.println("got service ID " + serviceID.toString());
    }
}

Echo client




package client;

import service.*;

import java.io.*;

import java.rmi.*; 

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.lease.LeaseRenewalManager;

import net.jini.lookup.LookupCache;

public class Client implements  ServiceDiscoveryListener {

    private LookupCache cache = null;

    public static void main(String[] args) {
	new Client();
    }

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

	// Build a cache of all discovered echo services and monitor changes
	ServiceDiscoveryManager serviceMgr = null;
	Class [] classes = new Class[] {Echo.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, 
							   null);	
	try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null,  // unicast locators
                                           null); // DiscoveryListener
            serviceMgr = new ServiceDiscoveryManager(mgr, 
						    new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
	
        try {
            cache = serviceMgr.createLookupCache(template, 
                                                null,  // no filter
                                                this); // listener
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }

	loop();
    }

    public void loop() {
	for (int n = 0; n < 10; n++) {
	    System.out.println("Sending: hello " + n);
	    echoToAll("hello " +  n);
	    try {
		Thread.currentThread().sleep(1000);
	    } catch(Exception e) {
	    }
	}
	System.exit(0);
    }

    private void echoToAll(String txt) {
	ServiceItem[] items = cache.lookup(null, Integer.MAX_VALUE);
	for (int n  = 0; n < items.length; n++) {
	    Echo svc = (Echo) items[n].service;
	    echoToOne(svc, txt);
	}
    }

    private void echoToOne(Echo svc, String txt) {
	String reply = null;
	try {
	    reply = svc.echo(txt);
	    System.out.println(reply);
	} catch(RemoteException e) {
	    System.out.println(e);
	}
    }

    public void serviceAdded(ServiceDiscoveryEvent evt) {
	// evt.getPreEventServiceItem() == null
	ServiceItem postItem = evt.getPostEventServiceItem();
	System.out.println("Service appeared: " +
			   postItem.service.getClass().toString());
    }

    public void serviceChanged(ServiceDiscoveryEvent evt) {
	ServiceItem preItem = evt.getPostEventServiceItem();
	ServiceItem postItem = evt.getPreEventServiceItem() ;
	System.out.println("Service changed: " +
			   postItem.service.getClass().toString());
    }
    public void serviceRemoved(ServiceDiscoveryEvent evt) {
	// evt.getPostEventServiceItem() == null
	ServiceItem preItem = evt.getPreEventServiceItem();
	System.out.println("Service disappeared: " +
			   preItem.service.getClass().toString());
    }

}

Echo listener


package client;

import service.*;

import java.io.*;

import java.rmi.*; 

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.lease.LeaseRenewalManager;

// Change start import java.rmi.server.ExportException; import net.jini.export.*; import net.jini.jrmp.JrmpExporter; import net.jini.core.event.*;
// Change end import net.jini.lookup.LookupCache;
// Change start public class ClientListener implements ServiceDiscoveryListener, RemoteEventListener {
// Change end private LookupCache cache = null;
// Change start private Remote proxy;
// Change end public static void main(String[] args) { ClientListener cl = new ClientListener();
// Change start // This is a hack to keep the server alive. Object keepAlive = new Object(); synchronized(keepAlive) { try { // Wait for a "notify" from another thread // that will never be sent. // So we stay alive for ever keepAlive.wait(); } catch(java.lang.InterruptedException e) { // do nothing } }
// Change end } public ClientListener() { System.setSecurityManager(new RMISecurityManager()); discoverOtherServices(); // When this registers as an event listener it gives an // object to the service to call notify() on it // The object must be a proxy for this Exporter exporter = new JrmpExporter(); try { proxy = exporter.export(this); } catch(ExportException e) { System.exit(1); } } private void discoverOtherServices() { // Build a cache of all discovered echo services and monitor changes ServiceDiscoveryManager serviceMgr = null; Class [] classes = new Class[] {Echo.class}; ServiceTemplate template = new ServiceTemplate(null, classes, null); try { LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS, null, // unicast locators null); // DiscoveryListener serviceMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager()); } catch(Exception e) { e.printStackTrace(); System.exit(1); } try { cache = serviceMgr.createLookupCache(template, null, // no filter this); // listener } catch(Exception e) { e.printStackTrace(); System.exit(1); } } public void serviceAdded(ServiceDiscoveryEvent evt) { // evt.getPreEventServiceItem() == null ServiceItem postItem = evt.getPostEventServiceItem(); System.out.println("Service appeared: " + postItem.service.getClass().toString());
// Change start Echo echo = (Echo) postItem.service; try { echo.addEventListener((RemoteEventListener) proxy); } catch(RemoteException e) { e.printStackTrace(); }
// Change end } public void serviceChanged(ServiceDiscoveryEvent evt) { ServiceItem preItem = evt.getPostEventServiceItem(); ServiceItem postItem = evt.getPreEventServiceItem() ; System.out.println("Service changed: " + postItem.service.getClass().toString()); } public void serviceRemoved(ServiceDiscoveryEvent evt) { // evt.getPostEventServiceItem() == null ServiceItem preItem = evt.getPreEventServiceItem(); System.out.println("Service disappeared: " + preItem.service.getClass().toString()); }
// Change start public void notify(RemoteEvent e) { System.out.println("Echo service has been called "); String text = null; try { Echo echoSrc = (Echo) e.getSource(); text = echoSrc.getChangedText(); System.out.println("Text changed to " + text); } catch(RemoteException re) { re.printStackTrace(); } }
// Change end }


Flashing clocks


Timer


/**
 * Timer service
 */
package service;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;
import net.jini.core.event.*;

public interface Timer extends Remote {

// Change start from clock without events public static final int VALID = 1; // event types public static final int INVALID = 2;
// Change end public void setTime(Date t) throws RemoteException; public Date getTime() throws RemoteException; public boolean isValidTime() throws RemoteException;
// Change start from clock without events public EventRegistration addEventListener(RemoteEventListener l) throws RemoteException;
// Change end }


Ticker Timer


package service;

import java.util.Date;
import net.jini.core.event.*;
import java.util.Vector;
import java.rmi.RemoteException;

public class TickerTimer implements Timer {
    private Date time;
    private boolean isValid;
    private Ticker ticker;
// Change start private Vector listeners = new Vector(); private int seqNum = 0; // event sequence counter
// Change end /** * Constructor with no starting time has * invalid timer and any time */ public TickerTimer() { time = new Date(0); isValid = false; ticker = new Ticker(time); ticker.start();
// Change start fireNotify(INVALID, seqNum++); // valid has changed state
// Change end } public TickerTimer(Date t) { time = t; isValid = true; ticker = new Ticker(time); ticker.start();
// Change start fireNotify(VALID, seqNum++); // valid has changed state
// Change end } public void setTime(Date t) { System.out.println("Setting time to " + t); time = t; isValid = true; if (ticker != null) { ticker.stopRunning(); } ticker = new Ticker(time); ticker.start();
// Change start fireNotify(VALID, seqNum++); // valid has changed state
// Change end } public Date getTime() { return ticker.getTime(); } public boolean isValidTime() { if (isValid) { return true; } else { return false; } }
// Change start public EventRegistration addEventListener(RemoteEventListener listener) { listeners.add(listener); return new EventRegistration(0L, this, null, 0L); }
// Change end
// Change start protected void fireNotify(long eventID, long seqNum) { RemoteEvent remoteEvent = new RemoteEvent(this, eventID, seqNum, null); for (int n = 0; n < listeners.size(); n++) { RemoteEventListener listener = (RemoteEventListener) listeners.elementAt(n); try { listener.notify(remoteEvent); } catch(UnknownEventException e) { // ignore } catch(RemoteException e) { // ignore } } }
// Change end } class Ticker extends Thread { private Date time; private boolean keepRunning = true; public Ticker(Date t) { time = t; } public Date getTime() { return time; } public void run() { while (keepRunning) { try { sleep(1000); } catch(InterruptedException e) { } time = new Date(time.getTime() + 1000); } } public void stopRunning() { keepRunning = false; } }


Listener


Validity Listener




package client;

import service.*;

import java.io.*;
import java.util.Date;

import java.rmi.*; 
import java.rmi.server.ExportException;
import net.jini.export.*; 
import net.jini.jrmp.JrmpExporter;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceRegistrar;
import java.rmi.RemoteException;

import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.LookupCache;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;

public class ValidityListener implements ServiceDiscoveryListener,
					 RemoteEventListener {

    private Timer timer;
    private Remote proxy;

    public static void main(String[] args) {
	ValidityListener validListen = new ValidityListener();

	// This is a hack to keep the server alive.
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		// Wait for a "notify" from another thread
		// that will never be sent.
		// So we stay alive for ever
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

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

	// When this registers as an event listener it gives an
	// object to the service to call notify() on it
	// The object must be a proxy for this
	Exporter exporter = new JrmpExporter();
	try {
	    proxy = exporter.export(this);
	} catch(ExportException e) {
	    System.exit(1);
	}

	// Build a cache of all discovered clocks and monitor changes
	ServiceDiscoveryManager serviceMgr = null;
        LookupCache cache = null;
	Class [] classes = new Class[] {Timer.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);	
	try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null,  // unicast locators
                                           null); // DiscoveryListener
            serviceMgr = new ServiceDiscoveryManager(mgr, 
						    new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
	
        try {
            cache = serviceMgr.createLookupCache(template, 
                                                null,  // no filter
                                                this); // listener
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

// Change start // an event has arrived public void notify(RemoteEvent evt) { System.out.println("Got event from " + evt.getSource()); String value = evt.getID() == Timer.VALID ? "valid" : "invalid"; System.out.println(" value is " + value); }
// Change end public void serviceAdded(ServiceDiscoveryEvent evt) { // evt.getPreEventServiceItem() == null ServiceItem postItem = evt.getPostEventServiceItem(); System.out.println("Service appeared: " + postItem.service.getClass().toString()); Timer timerSvc = (Timer) postItem.service;
// Change start try { // note the class cast: the proxy implements all the // interfaces of its service timerSvc.addEventListener((RemoteEventListener) proxy); } catch(RemoteException e) { e.printStackTrace(); }
// Change end } public void serviceChanged(ServiceDiscoveryEvent evt) { ServiceItem preItem = evt.getPostEventServiceItem(); ServiceItem postItem = evt.getPreEventServiceItem() ; System.out.println("Service changed: " + postItem.service.getClass().toString()); } public void serviceRemoved(ServiceDiscoveryEvent evt) { // evt.getPostEventServiceItem() == null ServiceItem preItem = evt.getPreEventServiceItem(); System.out.println("Service disappeared: " + preItem.service.getClass().toString()); // should remove proxy from event listener list } }


Deployment


Mutable file classifier

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

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


USER INTERFACE

User interface

UI or factory?

UI must know the proxy

Factory as Entry

UIFactory interface

This is all new


package ui;

import service.*;

import net.jini.core.entry.Entry;
import service.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.*;
import java.util.Date;
import java.rmi.RemoteException;

public interface UIFactory extends Entry {

    public JFrame createJFrame(Timer proxy);

}

UIFactory implementation

This is all new. It puts a label with the current time and a Refresh button inside a frame


package ui;

import net.jini.core.entry.Entry;
import service.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import java.rmi.RemoteException;

public class UIFactoryImpl implements UIFactory, ActionListener {

    private Timer proxy;
    private JLabel label;

    public UIFactoryImpl() {
    }

    public JFrame createJFrame(Timer proxy) {
	this.proxy = proxy;

	JFrame frame = new JFrame();
	Container contentPane = frame.getContentPane();
	BorderLayout layout = new BorderLayout();
	contentPane.setLayout(new BorderLayout());

	Date now = null;
	try {
	    now = proxy.getTime();
	} catch(RemoteException e) {
	    e.printStackTrace();
	}
	label = new JLabel(now.toString());
	contentPane.add(label, BorderLayout.CENTER);

	JButton btn = new JButton("Refresh");
	contentPane.add(btn, BorderLayout.SOUTH);
	btn.addActionListener(this);
	frame.pack();
	// let the client make this visible

	return frame;
    }

    public void actionPerformed(ActionEvent e) {
	// Refresh button was clicked
	Date now = null;
	try {
	    now = proxy.getTime();
	} catch(RemoteException re) {
	    re.printStackTrace();
	    return;
	}
	label.setText(now.toString());
    }
}

Service with UI




package device;

import service.*;
import ui.*;

import java.io.*;
import java.util.Date;

import java.rmi.*; 
import java.rmi.server.ExportException;
import net.jini.export.*; 
import net.jini.jrmp.JrmpExporter;
import net.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceRegistrar;
import java.rmi.RemoteException;
import net.jini.lookup.ServiceIDListener;
import net.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscoveryManager;

import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.LookupCache;
import net.jini.core.entry.Entry;

public class ClockDevice implements ServiceIDListener, ServiceDiscoveryListener {

    private Timer timer;

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

	// Build a cache of all discovered clocks and monitor changes
	ServiceDiscoveryManager serviceMgr = null;
        LookupCache cache = null;
	Class [] classes = new Class[] {Timer.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);	
	try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null,  // unicast locators
                                           null); // DiscoveryListener
            serviceMgr = new ServiceDiscoveryManager(mgr, 
						    new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
	
        try {
            cache = serviceMgr.createLookupCache(template, 
                                                null,  // no filter
                                                this); // listener
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void setTimer(Timer t) {
	timer = t;
	System.out.println("Our timer service is " + t);

	Exporter exporter = new JrmpExporter();

	// export a Timer proxy
	Remote proxy = null;
	try {
	    proxy = exporter.export(timer);
	} catch(ExportException e) {
	    System.exit(1);
	}

// Change start // Create a UI object Entry uiEntry = new UIFactoryImpl(); Entry[] entries = {uiEntry};
// Change end // Register with all lookup services as they are discovered JoinManager joinMgr = null; try { LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS, null, // unicast locators null); // DiscoveryListener
// Change start // note UI entry objects added to JoinManager joinMgr = new JoinManager(proxy, // service proxy entries, // attr sets - contains UI this, // ServiceIDListener mgr, // DiscoveryManager new LeaseRenewalManager());
// Change end } catch(Exception e) { e.printStackTrace(); System.exit(1); } } public void serviceIDNotify(ServiceID serviceID) { // called as a ServiceIDListener // Should save the id to permanent storage System.out.println("got service ID " + serviceID.toString()); } public void serviceAdded(ServiceDiscoveryEvent evt) { // evt.getPreEventServiceItem() == null ServiceItem postItem = evt.getPostEventServiceItem(); System.out.println("Service appeared: " + postItem.service.getClass().toString()); tryClockValidation((Timer) postItem.service); } public void serviceChanged(ServiceDiscoveryEvent evt) { ServiceItem preItem = evt.getPostEventServiceItem(); ServiceItem postItem = evt.getPreEventServiceItem() ; System.out.println("Service changed: " + postItem.service.getClass().toString()); } public void serviceRemoved(ServiceDiscoveryEvent evt) { // evt.getPostEventServiceItem() == null ServiceItem preItem = evt.getPreEventServiceItem(); System.out.println("Service disappeared: " + preItem.service.getClass().toString()); } private void tryClockValidation(Timer otherTimer) { try { if (timer.isValidTime() && ! otherTimer.isValidTime()) { // other clock needs to be set by us otherTimer.setTime(timer.getTime()); } else if (! timer.isValidTime() && otherTimer.isValidTime()) { // we need to be set from the other clock timer.setTime(otherTimer.getTime()); } } catch(RemoteException e) { // ignore other timer! } } public void setTime(Date t) throws RemoteException { timer.setTime(t); } public Date getTime() throws RemoteException { return timer.getTime(); } public boolean isValidTime() throws RemoteException { return timer.isValidTime(); } }

UI client


/**
 * Listen for state changes in clocks
 */

package client;

import service.*;
import ui.*;

import java.io.*;
import javax.swing.JFrame;

import java.rmi.*; 
import java.rmi.server.*;

import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.LookupCache;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.entry.Entry;
import net.jini.export.*; 
import net.jini.jrmp.JrmpExporter;

public class ClockUI  implements Remote, ServiceDiscoveryListener
{
    public static void main(String[] args) {
	ClockUI client = new ClockUI();

	// This is a hack to keep the server alive.
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		// Wait for a "notify" from another thread
		// that will never be sent.
		// So we stay alive for ever
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

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

	// Build a cache of all discovered clocks and monitor changes
	ServiceDiscoveryManager serviceMgr = null;
        LookupCache cache = null;
	Class [] classes = new Class[] {Timer.class};
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);	
	try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null,  // unicast locators
                                           null); // DiscoveryListener
            serviceMgr = new ServiceDiscoveryManager(mgr, 
						    new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
	
        try {
            cache = serviceMgr.createLookupCache(template, 
                                                null,  // no filter
                                                this); // listener
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

// Change start private void showClockUI(Timer service, Entry[] attributes) { for (int n = 0; n < attributes.length; n++) { Entry entry = attributes[n]; System.out.println("Entry is " + entry); if (entry instanceof UIFactory) { UIFactory factory = (UIFactory) entry; JFrame frame = factory.createJFrame(service); frame.setVisible(true); } } }
// Change end public void serviceAdded(ServiceDiscoveryEvent evt) { // assert: evt.getPreEventServiceItem() == null ServiceItem postItem = evt.getPostEventServiceItem(); System.out.println("Service appeared: " + postItem.service.getClass().toString()); Timer timerSvc = (Timer) postItem.service;
// Change start showClockUI(timerSvc, postItem.attributeSets);
// Change end } public void serviceChanged(ServiceDiscoveryEvent evt) { ServiceItem preItem = evt.getPostEventServiceItem(); ServiceItem postItem = evt.getPreEventServiceItem() ; System.out.println("Service changed: " + postItem.service.getClass().toString()); } public void serviceRemoved(ServiceDiscoveryEvent evt) { // assert: evt.getPostEventServiceItem() == null ServiceItem preItem = evt.getPreEventServiceItem(); System.out.println("Service disappeared: " + preItem.service.getClass().toString()); } }

Deployment

Problems with this approach

Run

Two clocks running with their "local" UIs on the right and their "remote UIs" on the left look like

(Don't worry that the times are all wrong - we only get the time at one instant.)


User interface - old stuff

Reasons for UIDescriptor


Attributes of UIDescriptor

role
Each UI may have a speecific role, such as system administrator UI, novice UI, general UI. The only UI role currently defined is


public interface net.jini.lookup.ui.MainUI {
    String ROLE = "net.jini.lookup.ui.MainUI";
}   
      
toolkit
A UI built using AWT components will require the java.awt toolkit to be present; using Swing will require the javax.swing toolkit; using MIDP would require the javax.microedition.lcdui. The toolkit is set to one of these strings, so that the client can do a quick check on what the UI requires
factory
The UI will generate objects of a certain type that can be used by the client. These could be e.g. The factory is a generator of one of these objects. It will be sent as a MarshalledObject (see later)

There is a factory interface for each type e.g


package net.jini.lookup.ui.factory;

import javax.swing.JFrame;

public interface JFrameFactory {
    String TOOLKIT = "javax.swing";
    String TYPE_NAME = "net.jini.lookup.ui.factory.JFrameFactory";

    JFrame getJFrame(Object roleObject);
}
      
attributes
These can carry any additional information about a UI. In particular, info about the factory type should be included

        Set attribs = new HashSet();
        Set typeNames = new HashSet();
        typeNames.add(JFrameFactory.TYPE_NAME);
        attribs.add(new UIFactoryTypes(typeNames));
      


Marshalled factory object


Factory implementation


FileClassifierFrame


Server

A server to deliver both a FileClassifier and a UI for it is


import complete.FileClassifierImpl;

import net.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceRegistrar;
import java.rmi.RemoteException;
import net.jini.lookup.ServiceIDListener;
import net.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.core.entry.Entry;

import net.jini.lookup.ui.MainUI;
import net.jini.lookup.ui.factory.FrameFactory;
import net.jini.lookup.entry.UIDescriptor;
import net.jini.lookup.ui.attribute.UIFactoryTypes;

import java.rmi.MarshalledObject;
import java.io.IOException;
import java.util.Set;
import java.util.HashSet;

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

        // stay around forever
        Object keepAlive = new Object();
        synchronized(keepAlive) {
            try {
                keepAlive.wait();
            } catch(InterruptedException e) {
                // do nothing
            }
        }
    }

    public FileClassifierServer() {

        JoinManager joinMgr = null;

        // The typenames for the factory
        Set typeNames = new HashSet();
        typeNames.add(FrameFactory.TYPE_NAME);

        // The attributes set
        Set attribs = new HashSet();
        attribs.add(new UIFactoryTypes(typeNames));

        // The factory
        MarshalledObject factory = null;
        try {
            factory = new MarshalledObject(new FileClassifierFrameFactory());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(2);
        }
        UIDescriptor desc = new UIDescriptor(MainUI.ROLE,
                                             FileClassifierFrameFactory.TOOLKIT,
                                             attribs,
                                             factory);

        Entry[] entries = {desc};

        try {
            LookupDiscoveryManager mgr = 
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null /* unicast locators */,
                                           null /* DiscoveryListener */);
            joinMgr = new JoinManager(new FileClassifierImpl(), /* service */
                                      entries /* attr sets */,
                                      this /* ServiceIDListener*/,
                                      mgr /* DiscoveryManagement */,
                                      new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void serviceIDNotify(ServiceID serviceID) {
        // called as a ServiceIDListener
        // Should save the id to permanent storage
        System.out.println("got service ID " + serviceID.toString());
    }
    
} // FileClassifierServer


A client

A client to get a FileClassifier and use its UI is


import common.FileClassifier;
import common.MIMEType;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.core.lookup.ServiceItem;
import net.jini.lease.LeaseRenewalManager;
import net.jini.core.entry.Entry;

import net.jini.lookup.ui.MainUI;
import net.jini.lookup.ui.factory.FrameFactory;
import net.jini.lookup.entry.UIDescriptor;
import net.jini.lookup.ui.attribute.UIFactoryTypes;

import java.awt.*;
import javax.swing.*;

import java.util.Set;
import java.util.Iterator;
import java.net.URL;

/**
 * TestFrameUI.java
 */

public class TestFrameUI {

    private static final long WAITFOR = 100000L;

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

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

    public TestFrameUI() {
        ServiceDiscoveryManager clientMgr = null;

        System.setSecurityManager(new RMISecurityManager());

        try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null /* unicast locators */,
                                           null /* DiscoveryListener */);
            clientMgr = new ServiceDiscoveryManager(mgr, 
                                                new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
  
        Class [] classes = new Class[] {FileClassifier.class};
        UIDescriptor desc = new UIDescriptor(MainUI.ROLE, 
                                             FrameFactory.TOOLKIT, 
                                             null, null);
        Entry [] entries = {desc};

        ServiceTemplate template = new ServiceTemplate(null, classes, 
                                                       entries);

        ServiceItem item = null;
        try {
            item = clientMgr.lookup(template, 
                                    null, /* no filter */ 
                                    WAITFOR /* timeout */);
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        
        if (item == null) {
            // couldn't find a service in time
            System.out.println("no service");
            System.exit(1);
        }

        // We now have a service that plays the MainUI role and
        // uses the FrameFactory toolkit of "java.awt".
        // We now have to find if there is a UIDescriptor
        // with a Factory generating an AWT Frame
        checkUI(item);
    }

    private void checkUI(ServiceItem item) {
        // Find and check the UIDescriptor's
        Entry[] attributes = item.attributeSets;
        for (int m = 0; m < attributes.length; m++) {
            Entry attr = attributes[m];
            if (attr instanceof UIDescriptor) {
                // does it deliver an AWT Frame?
                checkForAWTFrame(item, (UIDescriptor) attr);
            }
        }
    }
   
    private void checkForAWTFrame(ServiceItem item, UIDescriptor desc) {
        Set attributes = desc.attributes;
        Iterator iter = attributes.iterator();
        while (iter.hasNext()) {
            // search through the attributes, to find a UIFactoryTypes
            Object obj = iter.next();
            if (obj instanceof UIFactoryTypes) {
                UIFactoryTypes types = (UIFactoryTypes) obj;
                // see if it produces an AWT Frame Factory
                if (types.isAssignableTo(FrameFactory.class)) {
                    FrameFactory factory = null;
                    try {
                        factory = (FrameFactory) desc.getUIFactory(this.getClass().
                                                                  getClassLoader());
                    } catch(Exception e) {
                        e.printStackTrace();
                        continue;
                    }

                    Frame frame = factory.getFrame(item); 
                    frame.setVisible(true);
                } 
            }
        }
    }
        

} // TestFrameUI

Summary



Jan Newmarch (http://jan.newmarch.name)
jan@newmarch.name
Last modified: Mon May 24 14:22:33 EST 2004
Copyright ©Jan Newmarch
Copyright © Jan Newmarch, Monash University, 2007
Creative Commons License This work is licensed under a Creative Commons License
The moral right of Jan Newmarch to be identified as the author of this page has been asserted.