Jini

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


Events


GUI


Transactions


Jini echo

Echo service done in Jini The source files are here

Echo interface


package service;

import java.rmi.*;

public interface Echo extends Remote {
    String echo(String in) throws RemoteException;
}

Echo interface implementation



package server;

import service.*;

public class EchoImpl implements Echo {

    public EchoImpl() {
    }

    public String echo(String in) {
	return "Echo: " + in;
    }
}

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 {

    private Remote echo; // the service: it needs to be declared
                         // at this level to stop it being
                         // garbage collected

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

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

}

Compiling/running the example


Flashing clocks in Jini

Java classes for timer service

Code is available from here

Timer service


/**
 * Timer service
 */
package service;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;

public interface Timer extends Remote {
 
    public void setTime(Date t) throws RemoteException;

    public Date getTime() throws RemoteException;

    public boolean isValidTime() throws RemoteException;;


}

Computer Timer

Implementation of timer that gets its time from the computer clock using the Calendar class. This is the same as the UPnP service, except it uses java.util.Date


package service;

import java.util.Date;

public class ComputerTimer implements Timer {

    public void setTime(Date t) {
	// void
    }

    public Date getTime() {
	return new Date();
    }

    public boolean isValidTime() {
	return true;
    }
}

Ticker Timer

Another implementation that runs a "ticker" in a thread to update the time. This timer is invalid until something else sets its time. This is the same as the UPnP service


package service;

import java.util.Date;

public class TickerTimer implements Timer {
    private Date time;
    private boolean isValid;
    private Ticker ticker;

    /**
     * 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();
    }

    public TickerTimer(Date t) {
	time = t;
	isValid = true;
	ticker = new Ticker(time);
	ticker.start();
    }
 
    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();
    }

    public Date getTime() {
	return ticker.getTime();
    }

    public boolean isValidTime() {
	if (isValid) {
	    return true;
	} else {
	    return false;
	}
    }
}

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

Clock device




package device;

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

public class ClockDevice implements ServiceIDListener, ServiceDiscoveryListener {

    private Timer timer;

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

	findOtherServices();
    }

    private void findOtherServices() {
	// 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);

	advertiseOurService(t);
    }

    private void advertiseOurService(Timer t) {
	Exporter exporter = new JrmpExporter();

	// export a Timer proxy
	Remote proxy = null;
	try {
	    proxy = exporter.export(timer);
	} 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);
	}
    } 
    
    /* Discovered a new lookup service - our service is now 
     * registered with it
     */
    public void serviceIDNotify(ServiceID serviceID) {
	// called as a ServiceIDListener
	// Should save the id to permanent storage
	System.out.println("got service ID " + serviceID.toString());
    }


    /* These 3 methods are called when other services are
     * discovered/lost//changed
     */
   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());
    }

    /* We have discovered another service. So we try to synch with it.
     * If we have a valid time and it doesn't, then set its time
     * If it has a valid time and we don't, then set our time
     */
    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!
	}
    }

    /* Three calls on our timer service
     * Just here so the frame holding this doesn't need to
     * see internals of the clock
     */
    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();
    }
}

Ticker clock

This creates a clock device, sets a ticker timer in it and starts the frame. This is basically the same as the UPnP version except in doesn't have to catch the parse error for the XML device description.



package clock;

import device.*;
import service.*;

public class TickerClock {
    
    public static void main(String args[]) 
    {
	ClockDevice clockDev = new ClockDevice();

	clockDev.setTimer(new TickerTimer());

	ClockFrame clock;
	if (args.length > 0) {
	    clock= new ClockFrame(clockDev, args[0]);
	} else {
	    clock = new ClockFrame(clockDev);
	}
	clock.start();
    }
}

Computer clock

This creates a clock device, sets a computer timer in it and starts the frame . Similar to the UPnP version



package clock;

import device.*;
import service.*;

public class ComputerClock {
    
    public static void main(String args[]) 
    {
	ClockDevice clockDev = new ClockDevice();
	
	clockDev.setTimer(new ComputerTimer());

	ClockFrame clock;
	if (args.length > 0) {
	    clock= new ClockFrame(clockDev, args[0]);
	} else {
	    clock = new ClockFrame(clockDev);
	}
	clock.start();    }
}

Clock frame

This is the same as the UPnP version


/******************************************************************
 *
 *	CyberUPnP for Java
 *
 *	Copyright (C) Satoshi Konno 2002-2003
 *
 *	File : SampleClock.java
 *
 ******************************************************************/

package clock;

import device.*;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class ClockFrame extends JFrame implements Runnable, WindowListener
{
    private final static String DEFAULT_TITLE = "CyberLink Sample Clock";
    private ClockDevice clockDev;
    private ClockPane clockPane;

    public ClockFrame(ClockDevice clockDev) {
	this(clockDev, DEFAULT_TITLE);
    }

    public ClockFrame(ClockDevice clockDev, String title)
    {
	super(title);

	this.clockDev = clockDev;
	
	getContentPane().setLayout(new BorderLayout());
	
	clockPane = new ClockPane(clockDev);		
	getContentPane().add(clockPane, BorderLayout.CENTER);
	
	addWindowListener(this);
	
	pack();
	setVisible(true);
    }
    
    public ClockPane getClockPanel()
    {
	return clockPane;
    }
    
    public ClockDevice getClockDevice()
    {
	return clockDev;
    }
    
    ////////////////////////////////////////////////
    //	run	
    ////////////////////////////////////////////////
    
    private Thread timerThread = null;
    
    public void run()
    {
	Thread thisThread = Thread.currentThread();
	
	while (timerThread == thisThread) {
	    // getClockDevice().update();
	    getClockPanel().repaint();
	    try {
		Thread.sleep(1000);
	    }
	    catch(InterruptedException e) {}
	}
    }
    
    public void start()
    {
	// clockDev.start();
	
	timerThread = new Thread(this);
	timerThread.start();
    }
    
    public void stop()
    {
	// clockDev.stop();
	timerThread = null;
    }
    
    ////////////////////////////////////////////////
    //	main
    ////////////////////////////////////////////////
    
    public void windowActivated(WindowEvent e) 
    {
    }
    
    public void windowClosed(WindowEvent e) 
    {
    }
    
    public void windowClosing(WindowEvent e) 
    {
	stop();
	System.exit(0);
    }
    
    public void windowDeactivated(WindowEvent e) 
    {
    }
    
    public void windowDeiconified(WindowEvent e) 
    {
    }
    
    public void windowIconified(WindowEvent e) 
    {
    }
    
    public void windowOpened(WindowEvent e)
    {
    }

}

Clock pane

This is like the UPnP version, but calls to remote objects have to catch a potential RemoteException


/******************************************************************
 *
 *	CyberUPnP for Java
 *
 *	Copyright (C) Satoshi Konno 2002
 *
 *	File : SampleClockPane.java
 *
 ******************************************************************/

package clock;

import device.*;

import java.io.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.rmi.RemoteException;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class ClockPane extends JPanel
{
    private ClockDevice clockDev;
    private Color lastBlink = Color.BLACK;
    // private DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);    
    private DateFormat dateFormat = new SimpleDateFormat("kk:mm:ss");

    public ClockPane(ClockDevice clockDev)
    {
	this.clockDev = clockDev;
	loadImage();
	initPanel();
    }
    
    ////////////////////////////////////////////////
    //	Background
    ////////////////////////////////////////////////
    
    private final static String CLOCK_PANEL_IMAGE = "images/clock.jpg";
    
    private BufferedImage panelmage;
    
    private void loadImage()
    {
	File f = new File(CLOCK_PANEL_IMAGE);
	try {
	    panelmage = ImageIO.read(f);
	}
	catch (Exception e) {
	    // Debug.warning(e);
	}
    }
    
    private BufferedImage getPaneImage()
    {
	return panelmage;
    }
    
    ////////////////////////////////////////////////
    //	Background
    ////////////////////////////////////////////////
    
    private void initPanel()
    {
	BufferedImage panelmage = getPaneImage();
	setPreferredSize(new Dimension(panelmage.getWidth(), panelmage.getHeight()));
    }
    
    ////////////////////////////////////////////////
    //	Font
    ////////////////////////////////////////////////
    
    private final static String DEFAULT_FONT_NAME = "Lucida Console";
    private final static int DEFAULT_TIME_FONT_SIZE = 48;
    private final static int DEFAULT_DATE_FONT_SIZE = 18;
    private final static int DEFAULT_SECOND_BLOCK_HEIGHT = 8;
    private final static int DEFAULT_SECOND_BLOCK_FONT_SIZE = 10;
    
    private Font timeFont = null;
    private Font dateFont = null;
    private Font secondFont = null;
    
    private Font getFont(Graphics g, int size)
    {
	Font font = new Font(DEFAULT_FONT_NAME, Font.PLAIN, size);
	if (font != null)
	    return font;
	return g.getFont();
    }
    
    private Font getTimeFont(Graphics g)
    {
	if (timeFont == null)
	    timeFont = getFont(g, DEFAULT_TIME_FONT_SIZE);
	return timeFont;
    }
    
    private Font getDateFont(Graphics g)
    {
	if (dateFont == null)
	    dateFont = getFont(g, DEFAULT_DATE_FONT_SIZE);
	return dateFont;
    }
    
    private Font getSecondFont(Graphics g)
    {
	if (secondFont == null)
	    secondFont = getFont(g, DEFAULT_SECOND_BLOCK_FONT_SIZE);
	return secondFont;
    }
    
    ////////////////////////////////////////////////
    //	paint
    ////////////////////////////////////////////////
    
    private void drawClockInfo(Graphics g)
    {
	int winWidth = getWidth();
	int winHeight = getHeight();
	
	boolean valid = false;
	try {
	    valid = clockDev.isValidTime();
	} catch(RemoteException e) {
	    // valid is already false
	}

	if (valid) {
	    g.setColor(Color.BLACK);
	} else {
	    if (lastBlink == Color.WHITE) {
		g.setColor(Color.BLACK);
		lastBlink = Color.BLACK;
	    } else {
		g.setColor(Color.WHITE);
		lastBlink = Color.WHITE;
	    }
	}
	
	//// Time String ////
	Date now = null;
	try {
	    now = clockDev.getTime();
	} catch(RemoteException e) {
	    now = new Date(0);
	}
	String timeStr = dateFormat.format(now);
	
	Font timeFont = getTimeFont(g);
	g.setFont(timeFont);
	
	FontMetrics timeFontMetric = g.getFontMetrics();
	Rectangle2D timeStrBounds = timeFontMetric.getStringBounds(timeStr, g);
	
	int timeStrWidth = (int)timeStrBounds.getWidth();		
	int timeStrHeight = (int)timeStrBounds.getHeight();
	int timeStrX = (winWidth-timeStrWidth)/2;
	int timeStrY = (winHeight+timeStrHeight)/2;
	int timeStrOffset = timeStrHeight/8/2;
	g.drawString(
		     timeStr,
		     timeStrX,
		     timeStrY);
	
	//// Date String ////
	
	String dateStr = "Time";
	
	Font dateFont = getDateFont(g);
	g.setFont(dateFont);
	
	FontMetrics dateFontMetric = g.getFontMetrics();
	Rectangle2D dateStrBounds = dateFontMetric.getStringBounds(dateStr, g);
	
	g.drawString(
		     dateStr,
		     (winWidth-(int)dateStrBounds.getWidth())/2,
		     timeStrY-timeStrHeight-timeStrOffset);
	

    }
    
    private void clear(Graphics g)
    {
	g.setColor(Color.GRAY);
	g.clearRect(0, 0, getWidth(), getHeight());
    }
    
    
    private void drawPanelImage(Graphics g)
    {
	g.drawImage(getPaneImage(), 0, 0, null);
    }
    
    public void paint(Graphics g)
    {
	clear(g);
	drawPanelImage(g);
	drawClockInfo(g);
    }
}

Compiling/running the example

Why is it so complicated?

Jini uses mobile code to put a proxy on a client and make RPC calls from the proxy through to the service. This means

Do you need HTTP servers?

Running a lookup server

Sigh, as if that wasn't enough...


Jan Newmarch <jan@newmarch.name>
Last modified: Thu May 27 18:44:23 EST 2004
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.