Echo service done in Jini The source files are here
package service;
import java.rmi.*;
public interface Echo extends Remote {
String echo(String in) throws RemoteException;
}
package server;
import service.*;
public class EchoImpl implements Echo {
public EchoImpl() {
}
public String echo(String in) {
return "Echo: " + in;
}
}
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());
}
}
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());
}
}
build.xml
file.
ant compile
ant runserver
ant runclient
${jini_home}/lib/jini-core.jar
${jini_home}/lib/jini-ext.jar
${jini_home}/lib/sun-util.jar
These files are in the Jini 2.0 zip file at
ftp://jan.newmarch.name/pub/jini-1_2_1-src.zip
javac -classpath=... SourceFile
policy.all
with contents
grant {
permission java.security.AllPermission "", "";
};
-Djava.security.policy="policy file"
rmic -v1.2 server.EchoImpl
copy server/EchoImpl_Stub.class HTTP_server_root/classes
-Djava.rmi.server.codebase=http://${hostname}/classes/
.
java -classpath=... -Djava.rmi.server.codebase=... -Djava.security.policy=... ClassFile
RemoteException
Remote
and all methods
must throw a RemoteException
java.util.Date
is used
/**
* 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;;
}
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;
}
}
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;
}
}
JoinManager
ServiceDiscoveryManager
serviceAdded()
, etc.
The "device" can make method calls on the remote services and also to its
own local service
RemoteException
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();
}
}
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();
}
}
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(); }
}
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)
{
}
}
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);
}
}
build.xml
file.
ant compile
ant runtickerclock
ant runcomputerclock
${jini_home}/lib/jini-core.jar
${jini_home}/lib/jini-ext.jar
${jini_home}/lib/sun-util.jar
These files are in the Jini 2.0 zip file at
ftp://jan.newmarch.name/pub/jini-1_2_1-src.zip
policy.all
with contents
grant {
permission java.security.AllPermission "", "";
};
-Djava.security.policy="policy file"
rmic -v1.2 service.ComputerTimer
rmic -v1.2 service.TickerTimer
copy {ComputerTimer|TickerTimer}_Stub.class HTTP_server_root/classes
-Djava.rmi.server.codebase=http://${hostname}/classes/
.
javac -classpath=... SourceFile
java -classpath=... -Djava.rmi.server.codebase=... -Djava.security.policy=... ClassFile
Jini uses mobile code to put a proxy on a client and make RPC calls from the proxy through to the service. This means
_Stub
classes)
reggie-dl.jar
in its classpath; the client won't have the service's files in its
classpath, or the reggie-dl.jar
either, etc
Sigh, as if that wasn't enough...
java -jar $JINI_HOME/lib/tools.jar -port 8080 -dir $JINI_HOME/lib
$JINI_HOME/reggie/start.policy
java $JINI_HOME/lib/start.jar start-transient-reggie.config
The config file says (in a complicated - but flexible - way) "start a LUS"
import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.config.ConfigUtil;
com.sun.jini.start {
private static codebase = "http://192.168.1.11:8080/reggie-dl.jar";
private static policy = "/usr/local/reggie/reggie.policy";
private static classpath = "/usr/local/jini2_0/lib/reggie.jar";
private static config = "/usr/local/reggie/transient-reggie.config";
static serviceDescriptors = new ServiceDescriptor[] {
new NonActivatableServiceDescriptor(
codebase, policy, classpath,
"com.sun.jini.reggie.TransientRegistrarImpl",
new String[] { config })
};
}
/usr/local/reggie/transient-reggie.config
. The contents
of this could be
com.sun.jini.reggie {
initialMemberGroups = new String[] {};
}