|
Using Other ServicesIn all the examples so far, a proxy has been created in a server and registered with a lookup service. Meanwhile, a service backend has usually been left behind in the server to handle calls from the proxy. However, there may be no need for the service to exist on the server, and the proxy could make use of other services elsewhere. This may be subject to security restrictions imposed by the client, which may disallow connections to some hosts . In this section, we shall give an example of using a non-Jini service on another host. Recently an Australian, Pat Farmer, attempted to set a world record for jogging the longest distance. While he was running around, I became involved in a small project to broadcast his heartbeat live to the Web; a heart monitor was attached to him, which talked via an RS232 link to a mobile phone he was carrying. This did a data transfer to a program running at http://www.micromed.com.au located at the Gold Coast, which forwarded the data to a machine at the Distributed Systems Technology Centre (DSTC) in Brisbane. This ran a Web server delivering an applet, and the applet talked back to a server on the DSTC machine, which sent out the data to each applet as it was received from the heart monitor. Now that the experiment is over, the broadcast data is sitting as a file at http://www.micromed.com.au/patfarmer/v2/patfhr.ecg , and it can be viewed on the applet from http://www.micromed.com.au/patfarmer/v2/heart.html . We can make it into a Jini service as follows :
The client shows what you see in Figure 9-9. The break towards the right-hand side shows where the current trace is being written (it scans from left to right, over-writing as it goes). Cardiologists do not seem to be concerned about the lack of horizontal or vertical scales , as long as the trace is physically the right size ! Figure 9-9: Heart monitor trace service The heart monitor service can be regarded in a couple of ways:
Many other non-RMI services can be built that act in this "fat proxy" style. Heart InterfaceThe Heart interface only has one method, and that is to show() the heart trace in some manner: /** * Heart.java */ package heart; public interface Heart extends java.io.Serializable { public void show(); } // Heart HeartServerThe HeartServer is similar to the method discussed in Chapter 8, of uploading a complete implementation of the service. This service, of type HeartImpl , is primed with a URL identifying where the heart data is stored. An HTTP server will later deliver this data. This implementation is enough to locate the service. However, rather than just getting anyone 's heart data, a client may wish to search for a particular person's data. This can be done by adding a Name entry as additional information about the service. A server that exports the complete service, plus the entry information, is as follows: package heart; 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; import net.jini.lease.LeaseRenewalManager; import net.jini.lease.LeaseListener; import net.jini.lease.LeaseRenewalEvent; import net.jini.core.entry.Entry; import net.jini.lookup.entry.Name; /** * HeartServer.java */ public class HeartServer implements DiscoveryListener, LeaseListener { protected LeaseRenewalManager leaseManager = new LeaseRenewalManager(); public static void main(String argv[]) { new HeartServer(); // keep server running forever to // - allow time for locator discovery and // - keep re-registering the lease Object keepAlive = new Object(); synchronized(keepAlive) { try { keepAlive.wait(); } catch(InterruptedException e) { // do nothing } } } public HeartServer() { 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 HeartImpl("http://_ www.micromed.com.au/patfarmer/v2/patfhr.ecg"); new Entry[] {new Name("Pat Farmer")}); ServiceRegistration reg = null; try { reg = registrar.register(item, Lease.FOREVER); } catch(java.rmi.RemoteException e) { System.err.println("Register exception: " + e.toString()); continue; } 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()); } } // HeartServer HeartClientThe client searches for a service implementing the Heart interface, with the additional requirement that it be for a particular person. Once it has this, the client just calls the show() method on the service to display this in some manner: package heart; import heart.Heart; 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; import net.jini.core.entry.Entry; import net.jini.lookup.entry.Name; /** * HeartClient.java */ public class HeartClient implements DiscoveryListener { public static void main(String argv[]) { new HeartClient(); // stay around long enough to receive replies try { Thread.currentThread().sleep(1000000L); } catch(java.lang.InterruptedException e) { // do nothing } } public HeartClient() { 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[] {Heart.class}; Entry [] entries = new Entry[] {new Name("Pat Farmer")}; Heart heart = null; ServiceTemplate template = new ServiceTemplate(null, classes, entries) for (int n = 0; n < registrars.length; n++) { System.out.println("Service found"); ServiceRegistrar registrar = registrars[n]; try { heart = (Heart) registrar.lookup(template); } catch(java.rmi.RemoteException e) { e.printStackTrace(); continue; } if (heart == null) { System.out.println("Heart null"); continue; } heart.show(); System.exit(0); } } public void discarded(DiscoveryEvent evt) { // empty } } // HeartClient Heart ImplementationThe HeartImpl class opens a connection to an HTTP server and requests delivery of a file. Heart data needs to be displayed at a reasonable rate, so it reads, draws, and sleeps, in a loop. It acts as a fat client to the HTTP server, displaying the data in a suitable format (in this case, it uses HTTP as a transport mechanism for data delivery). As a "client-aware" service, it customizes this delivery to the characteristics of the client platform, just occupying a "reasonable" amount of screen space and using local colors and fonts. /** * HeartImpl.java */ package heart; import java.io.*; import java.net.*; import java.awt.*; public class HeartImpl implements Heart { protected String url; /* * If we want to run it standalone we can use this */ public static void main(String argv[]) { HeartImpl impl = new HeartImpl("file:/home/jan/projects/jini/doc/heart/TECG3.ecg"); impl.show(); } public HeartImpl(String u) { url = u; } double[] points = null; Painter painter = null; String heartRate = "--"; public void setHeartRate(int rate) { if (rate > 20 && rate <= 250) { heartRate = "Heart Rate: " + rate; } else { heartRate = "Heart Rate: --"; } } public void quit(Exception e, String s) { System.err.println(s); e.printStackTrace(); System.exit(1) } public void show() { int SAMPLE_SIZE = 300 / Toolkit.getDefaultToolkit(). getScreenResolution(); Dimension size = Toolkit.getDefaultToolkit(). getScreenSize(); int width = (int) size.getWidth(); // capture points in an array, for redrawing in app if needed points = new double[width * SAMPLE_SIZE]; for (int n = 0; n < width; n++) { points[n] = 1; } URL dataUrl = null; InputStream in = null; try { dataUrl = new URL(url); in = dataUrl.openStream(); } catch (Exception ex) { quit(ex, "connecting to ECG server"); return; } Frame frame = new Frame("Heart monitor"); frame.setSize((int) size.getWidth()/2, (int) size.getHeight()/2); try { painter = new Painter(this, frame, in); painter.start(); } catch (Exception ex) { quit(ex, "fetching data from ECG server"); return; } frame.setVisible(true); } } // HeartImpl class Painter extends Thread { static final int DEFAULT_SLEEP_TIME = 25; // milliseconds static final int CLEAR_AHEAD = 15; static final int MAX = 255; static final int MIN = 0; final int READ_SIZE = 10; protected HeartImpl app; protected Frame frame; protected InputStream in; protected final int RESOLUTION = Toolkit.getDefaultToolkit(). getScreenResolution(); protected final int UNITS_PER_INCH = 125; protected final int SAMPLE_SIZE = 300 / RESOLUTION; protected int sleepTime = DEFAULT_SLEEP_TIME; public Painter(HeartImpl app, Frame frame, InputStream in) throws Exception { this.app = app; this.frame = frame; this.in = in; } public void run() { while (!frame.isVisible()) { try { Thread.sleep(1000); } catch(Exception e) { // ignore } } int height = frame.getSize().height; int width = frame.getSize().width; int x = 1; // start at 1 rather than 0 to avoid drawing initial line // from 128 .. 127 int magnitude; int nread; int max = MIN; // top bound of magnitude int min = MAX; // bottom bound of magnitude int oldMax = MAX + 1; byte[] data = new byte[READ_SIZE]; Graphics g = frame.getGraphics(); g.setColor(Color.red); try { Font f = new Font("Serif", Font.BOLD, 20); g.setFont(f); } catch (Exception ex) { // .... } try { boolean expectHR = false; // true ==> next byte is heartrate while ((nread = in.read(data)) != 1) { for (int n = 0; n < nread; n++) { int thisByte = data[n] & 0xFF; if (expectHR) { expectHR = false; app.setHeartRate(thisByte); continue; } else if (thisByte == 255) { expectHR = true; continue; } // we are reading bytes, from 127..128 // convert to unsigned magnitude = thisByte; // then convert to correct scale magnitude -= 128; // scale and convert to window coord from the top downwards int y = ((128 - magnitude) * RESOLUTION) / UNITS_PER_INCH; app.points[x] = y; // draw only on multiples of sample size if (x % SAMPLE_SIZE == 0) { // delay to draw at a reasonable rate Thread.sleep(sleepTime); int x0 = x / SAMPLE_SIZE; g.clearRect(x0, 0, CLEAR_AHEAD, height); if (oldMax != MAX + 1) { g.drawLine(x0-1, oldMax, x0, min); } g.drawLine(x0, min, x0, max); oldMax = max; min = 1000; max = 1000; if (app.heartRate != null) { g.setColor(Color.black); g.clearRect(0, 180, 200, 100); g.drawString(app.heartRate, 0, 220); g.setColor(Color.red); } } else { if (y > max) max = y; if (y < min) min = y; } if (++x >= width * SAMPLE_SIZE) { x = 0; } } } } catch(Exception ex) { if (! (ex instanceof SocketException)) { System.out.println("Applet quit -- got " + ex); } } finally { try { if (in != null) { in.close(); in = null; } } catch (Exception ex) { // hide it } } } } |