Chapter 11: Choices for Service Architecture

A client will only be looking for an implementation of an interface. The implementation can be done in many different ways, discussed in this chapter.

11.1. Proxy Choices

11.1.1 Proxy is the Service

One extreme is where the proxy is so fat, that there is nothing left to do on the server side. The role of the server is to register the proxy with service locators, and just to stay alive (renewing leases on the service locators). The service itself runs entirely within the client. A class diagram for the file classifier problem using this method is given in figure 11.1.

Figure 11.1: Class diagram for file classifier
We have already seen the full object diagram for the JVMs, but just concentrating on these classes looks like figure 11.2.
Figure 11.2: Objects in the JVMs

The client asks for a FileClassifier. What is uploaded to the service locators, and so what the client gets, is a FileClassifierImpl. The FileClassifierImpl runs entirely within the client, and does not communicate back to its server at all. This can also be done for any service, when the service is purely a software one which does not need any link back to the server. It could be something like a calendar which is independent of location, or a diary which uses files on the client side rather than the server side.

11.1.2 RMI Proxy

The opposite extreme to this is where all of the processing is done on the server side. The proxy just exists on the client to take calls from the client, invoke the method in the service on the server, and return the result to the client. Java's RMI does this in a fairly transparent way (once all the correct files and additional servers are set up!)

A class diagram for an implementation of the file classifier using this mechanism is shown in figure 11.3.

Figure 11.3: Class diagram for RMI proxy
The objects in the JVM's are shown in figure 11.4. .
Figure 11.4: JVM objects for RMI proxy
The full code for this is given in the section "RMI Proxy for FileClassifier".

The class structure for this is a little more complex in order to get RMI to work. The interface RemoteFileClassifier is defined for convenience (the FileClassifierImpl could have implemented FileClassifier and Remote directly). Before Jeri, it was customary for the implementation to subclass from the UnicastRemoteObject class, but now it is recommended to use methods of an Exporter object (not shown in the figure). Implementing the Remote interface allows the proxy to be generated which can call the methods of a FileClassifierImpl objects remotely.

This structure is useful when the service needs to do no processing on the client side, but does need to do a lot on the server side. For example, a diary that stores all information communally on the server rather than individually on each client. Services that are tightly linked to a piece of hardware on the server are other cases.

11.1.3 Non-RMI Proxy

If RMI is not used and the proxy and service want to share processing, then both the service and the proxy must be created explicitly on the server side. The proxy is explicitly registered with a lookup service,just as with an RMI proxy. The major differences are (a) the server creates the proxy and does not use an exporter for this; (b) the proxy must implement the interface, but the service need not do so since the proxy and service are not tightly linked by a class structure any more. The class diagram for the file classifier with this organisation is displayed in figure 11.5.

Figure 11.5: Class diagram for non-RMI proxy
. and the JVM's at runtime are shown in figure 11.6.
Figure 11.6: JVM objects for non-RMI proxy

This doesn't specify how the proxy and the server communicate. They could open up a socket connection, for example and exchange messages using a message structure only they understand. Or they could communicate using a well-known protocol, such as HTTP for example: the proxy could make HTTP requests, and the service could act as an HTTP server handling these requests and returning documents. A version of the file classifier using sockets to communicate is given in the section "Non-RMI Proxy for FileClassifier".

This model is good for bringing "legacy" client-server applications into the Jini world. Client-server applications often communicate using a specialised protocol between the client and server. Copies of the client have to be distributed out to all machines. If there is a bug in the client, they all have to be updated which is often impossible. Worse, if there is a change to the procotol, then the server must be rebuilt to handle old and new versions while attempts are made to update all the clients. This is a tremendous problem with Web browsers for example, with varying degrees of support for HTML 3.2 and HTML 4.0 features, let alone new protocol extensions such as stylesheets and XML. CGI scripts that attempt to deliver the "right" version of documents to various browsers are clumsy but necessary hacks.

What can be done instead is to distribute a "shell" client, that just contacts the server and uploads a proxy. The proxy is the real "heart" of the client, whereas the service is the server part of the original client-server system. When changes occur, the service and its proxy can be updated together, and there is no need to make changes to the "shell' out on all the various machines.

11.1.4 RMI and Non-RMI Proxies

The last variation is to have a service, an explicit (smart) proxy and an RMI proxy. The RMI proxy is created from the service using an Exporter. The smart proxy is created by the server, and will typically be told about the RMI proxy in its consructor. The smart proxy is registered on a lookup service.

The RMI proxy can be used for RPC-like communication mechanism between the smart proxy and the service. This is just like the last case, but instead of requiring the smart proxy and service to implement their own communication protocol, the smart proxy calls methods on the local RMI proxy which uses RMI protocols to talk across the network to the service. The smart proxy and service can be of any relative size, just like in the last case. What this does is to simplify the task of the programmer in that no distributed protocol needs to be devised and implemented.

In the later section "RMI and non-RMI Proxies for FileClassifier" there is a non-RMI proxy FileClassifierProxy implementing the FileClassifier interface. This communicates with an object which implements the ExtendedFileClassifier interface. There is an object on the server of type ExtendedFileClassifierImpl and an RMI proxy for this on the client side of type ExtendedFileClassifierImpl_Stub The class diagram is shown in figure 11.7.

Figure 11.7: Class diagram for RMI and non-RMI proxies
While this looks complex, it is really just a combination of the last two cases. The proxy makes local calls on the RMI stub which makes remote calls on the service. The JVM's are displayed in figure 11.8.
Figure 11.8: JVM objects for RMI and non-RMI proxies

11.2. RMI Proxy for FileClassifier

An RMI proxy can be used when all of the work done by the service is done on the server side. Then the server makes available a thin proxy that simply channels method calls from the client across the network to the "real" service in the server, and returns the result back to the client. The programming for this is relatively simple. The service has to do two major things in its class structure:

  1. Implement Remote. This is because methods will be called on the service from the proxy, and these will be remote calls on the service

  2. Locate and use an Exporter object to create an RMI proxy

11.2.1 What doesn't change

The client is not concerned about the implementation of the service at all, and doesn't change. The FileClassifier interface doesn't change either, since this is fixed and used by any client and any service implementation. We have already declared its methods to throw RemoteException so a proxy is able to call its methods remotely. And the MIMEType doesn't change since we have already declared it to implement Serializable - it is passed back across the network from the service to its proxy

11.2.2 RemoteFileClassifier

The implementation will need to implement both the FileClassifier and the Remote interface. It is convenient to define another interface RemoteFileClassifier just to do this. This interface will be used fairly frequently in the rest of this book.



package rmi;

import common.FileClassifier;
import java.rmi.Remote;

/**
 * RemoteFileClassifier.java
 */

public interface RemoteFileClassifier extends FileClassifier, Remote {
        
} // RemoteFileClasssifier

11.2.3 FileClassifierImpl

The service itself will look like



package rmi;

import common.MIMEType;
import common.FileClassifier;

/**
 * FileClassifierImpl.java
 */

public class FileClassifierImpl implements RemoteFileClassifier {
    
    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
	System.out.println("Called with " + fileName);
        if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else
            // fill in lots of other types,
            // but eventually give up and
            return new MIMEType(null, null);
    }


    public FileClassifierImpl() throws java.rmi.RemoteException {
	// empty constructor required by RMI
    }
    
} // FileClassifierImpl

11.2.4 FileClassifierServer

The server changes by firstly getting an Exporter object and using this to create a proxy. This proxy implements RemoteFileClassifier as shown by the class cast, but it is only necessary for it to be a Remote object.



package rmi;

import rmi.FileClassifierImpl;
import rmi.RemoteFileClassifier;

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 net.jini.lease.LeaseRenewalManager;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import java.rmi.RMISecurityManager;

import net.jini.config.*; 
import net.jini.export.*; 

/**
 * FileClassifierServerRMI.java
 */

public class FileClassifierServerRMI implements DiscoveryListener, LeaseListener {

    protected FileClassifierImpl impl;
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();

    // explicit proxy for Jini 2.0
    protected RemoteFileClassifier proxy;
    private static String CONFIG_FILE = "jeri/file_classifier_server.config";
    
    public static void main(String argv[]) {
	new FileClassifierServerRMI();

	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

    public FileClassifierServerRMI() {
	try {
	    impl = new FileClassifierImpl();


	} catch(Exception e) {
            System.err.println("New impl: " + e.toString());
            System.exit(1);
	}

	String[] configArgs = new String[] {CONFIG_FILE};

	try {
	    // get the configuration (by default a FileConfiguration) 
	    Configuration config = ConfigurationProvider.getInstance(configArgs); 
	    
	    // and use this to construct an exporter
	    Exporter exporter = (Exporter) config.getEntry( "FileClassifierServer", 
							    "exporter", 
							    Exporter.class); 
	    // export an object of this class
	    proxy = (RemoteFileClassifier) exporter.export(impl);
	} catch(Exception e) {
	    System.err.println(e.toString());
	    e.printStackTrace();
	    System.exit(1);
	}

	// install suitable security manager
	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();
	RemoteFileClassifier service;

        for (int n = 0; n < registrars.length; n++) {
            ServiceRegistrar registrar = registrars[n];

	    // export the proxy service - use the actual proxy in 2.0
	    ServiceItem item = new ServiceItem(null,
					       proxy,
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.print("Register exception: ");
		e.printStackTrace();
		// System.exit(2);
		continue;
	    }
	    try {
		System.out.println("service registered at " +
				   registrar.getLocator().getHost());
	    } catch(Exception e) {
	    }
	    leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }
        
} // FileClassifierServerRMI

The server makes use of a configuration provider to locate a Configuration object and hence an Exporter. As before, the default Configuration object is a FileConfiguration which uses a configuration file (here given as jeri/file_classifier_server.config). For Jeri, the contents of this file are


import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

FileClassifierServer {
    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

If instead the older JRMP version of RMI is used, the configuration file would be

import net.jini.jrmp.*; 

FileClassifierServer {
    exporter = new JrmpExporter(); 
}

11.2.5 Jeri: What classes need to be where?

Using the new Jini ERI we have the classes

  1. common.MIMEType

  2. common.FileClassifier

  3. rmi.RemoteFileClassifier

  4. rmi.FileClassifierImpl

  5. rmi.FileClassifierServer

  6. client.TestFileClassifier

These could be running on upto four different machines
  1. The server machine for FileClassifierServer

  2. The HTTP server, which may be on a different machine

  3. The machine for the lookup service

  4. The machine running the client TestFileClassifier

What classes need to be known to which machines?

The server running FileClassifierServer needs to know the following classes and interfaces

  1. The common.FileClassifier interface

  2. The rmi.RemoteFileClassifier interface

  3. The class common.MIMEType

  4. The class rmi.FileClassifierServer

  5. The class rmi.FileClassifierImpl

The lookup service does not need to know any of these classes. It just deals with them in the form of a java.rmi.MarshalledObject

The client needs to know

  1. The common.FileClassifier interface

  2. The class common.MIMEType

In the older JRMP-style RMI, one of the main functions of the HTTP server was to download the rmic-generated RMI stub. Using Jeri, this is no longer needed. Does this mean the HTTP server isn't necessary any more? Regrettably, no. While the client knows the "commonly known" interfaces and gets the proxy, there is the RemoteFileClassifier interface which so far is only known on the server side. In order for the client to be able to unmarshall the proxy, it needs to get this interface from the HTTP server. Thus the HTTP server needs to be able to access

  1. The rmi.RemoteFileClassifier interface

What does the phrase "available" mean in the last paragraph? The HTTP server will look for files based on the java.rmi.server.codebase property of the application server. The value of this property is a URL. Often, URLs can be file or http references. But for this case, the URL will be used by clients running anywhere, so it cannot be a file reference specific to a particular machine. For the same reason, it cannot be just localhost - unless you are running every part of a Jini federation on a single computer!

If java.rmi.server.codebase is an http reference, then the above class files must be accessible from that reference. For example, suppose the property is set to


java.rmi.server.codebase=http://myWebHost/classes
(where myWebHost is the name of the HTTP server's host) and this Web server has its DocumentRoot set to /home/webdocs then these files must exist

/home/webdocs/classes/rmi/RemoteFileClassifier.class

An Ant file to build and deploy server files where the service uses Jeri (or any protocol that generates its own proxies at runtime) is



11.2.6 JRMP: What classes need to be where?

This section discusses Jini's use of RMI as it used to be before Jeri, and is what had to be done in Jini 1.2 and earlier. It is recommended not to use this any more.

We have the classes

  1. common.MIMEType

  2. common.FileClassifier

  3. rmi.RemoteFileClassifier

  4. rmi.FileClassifierImpl

  5. rmi.FileClassifierImpl_Stub

  6. rmi.FileClassifierServer

  7. client.TestFileClassifier

(The class FileClassifierImpl_Stub is added to our classes by rmic as discussed in the next section.) These could be running on upto four different machines
  1. The server machine for FileClassifierServer

  2. The HTTP server, which may be on a different machine

  3. The machine for the lookup service

  4. The machine running the client TestFileClassifier

What classes need to be known to which machines?

The server running FileClassifierServer needs to know the following classes and interfaces

  1. The common.FileClassifier interface

  2. The rmi.RemoteFileClassifier interface

  3. The class common.MIMEType

  4. The class rmi.FileClassifierServer

  5. The class rmi.FileClassifierImpl

The lookup service does not need to know any of these classes. It just deals with them in the form of a java.rmi.MarshalledObject

The client needs to know

  1. The common.FileClassifier interface

  2. The class common.MIMEType

In addition, the HTTP server needs to be able to load and store classes. It needs to be able to access

  1. The rmi.FileClassifierImpl_Stub interface

  2. The rmi.RemoteFileClassifier interface

The reason for all of these is slightly complex. In the FileClassifierProxy constructor, the class FileClassifierImpl is passed in. The RMI runtime converts this to FileClassifierImpl_Stub. This class implements the same interfaces as FileClassifierImpl: that is, RemoteFileClassifier also needs to be available.

What does the phrase "available" mean in the last paragraph? The HTTP server will look for files based on the java.rmi.server.codebase property of the application server. The value of this property is a URL. Often, URLs can be file or http references. But for this case, the URL will be used by clients running anywhere, so it cannot be a file reference specific to a particular machine. For the same reason, it cannot be just localhost - unless you are running every part of a Jini federation on a single computer!

If java.rmi.server.codebase is an http reference, then the above class files must be accessible from that reference. For example, suppose the property is set to


java.rmi.server.codebase=http://myWebHost/classes
(where myWebHost is the name of the HTTP server's host) and this Web server has its DocumentRoot set to /home/webdocs then these files must exist

/home/webdocs/classes/rmi/FileClassifierImpl_Stub.class
/home/webdocs/classes/rmi/RemoteFileClassifier.class
/home/webdocs/classes/common/FileClassifier.class
/home/webdocs/classes/common/MIMEType.class

11.2.7 Running the RMI Proxy FileClassifier

Again we have a server and a client to run. Calling the client is unchanged from the situation discussed in the previous chapter using the server in the complete package, since the client is independant of any server implementation


java -Djava.security.policy=policy.all client.TestFileClassifier

The value of java.rmi.server.codebase must specify the protocol used by the HTTP server to find the class files. This could be the file protocol or the http protocol. If the class files are stored on my Web server's pages under classes/rmi/FileClassifierImpl_Stub.class. the codebase would be specified as


java.rmi.server.codebase=http://myWebHost/classes/
(where myWebHost is the name of the HTTP's server host).

The server also sets a security manager. This is a restrictive one, so it needs to be told to allow access. This can be done by setting the property java.security.policy to point to a security policy file such as policy.all.

Combining all these points leads to startups such as


java -Djava.rmi.server.codebase=http://myWebHost/classes/ \
     -Djava.security.policy=policy.all \
     rmi.FileClassifierServerRMI

11.3. Non-RMI Proxy for FileClassifier

Many client-server programs communicate by message passing, often using a TCP socket. The two sides need to have an agreed protocol, that is they must have a standard set of message formats, and know what messages to receive and what replies to send at any time. Jini can be used in this sort of case by providing a wrapper around the client and server, and making them available as a Jini service. The original client then becomes a proxy agent for the server, and is distributed to Jini clients for execution. The original server runs within the Jini server and performs the real work of the service, just as in the thin proxy model. What differs is the class structure and how the components communicate.

The proxy and the service do not need to belong to the same class, or even share common superclasses. Unlike the RMI case, the proxy is not derived from the service so does not have a shared class structure. Both are written independently, using their own appropriate class hierarchies. The proxy still has to implement the FileClassifier interface, since that is what the client is asking for and the proxy is delivering.

If RMI is not used, then any other distributed communication mechanism can be employed. Typically client-server systems will use something like reliable TCP ports, but this is not the only choice. It is the one used in this example, though. So the service listens on an agreed port, the client connects to this port, and they exchange messages.

The message format adopted for this problem is really simple:

  1. The proxy sends a message giving the file extension that it wants classified. This can be sent as a newline terminated string

  2. The service will either succeed or fail in the classification. If it fails, it sends a single line of the string "null" followed by a newline. If it succeeds, it sends two lines, the first being the content type, the second the subtype.

The proxy will then use this reply to either return null or a new MIMEType object.

11.3.1 FileClassifierProxy

The proxy object will be exported completely to a Jini client such as TestFileClassifier. When this client calls the getMIMEType() method, the proxy opens up a connection on an agreed TCP port to the service and exchanges messages on this port. It then returns a suitable result. The code is



package socket;

import common.FileClassifier;
import common.MIMEType;
import java.net.Socket;

import java.io.Serializable;
import java.io.IOException;
import java.rmi.Naming;

import java.io.*;

/**
 * FileClassifierProxy
 */

public class FileClassifierProxy implements FileClassifier, Serializable {

    static public final int PORT = 2981;
    protected String host;

    public FileClassifierProxy(String host) {
	this.host = host;
    }

    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
	// open a connection to the service on port XXX
	int dotIndex = fileName.lastIndexOf('.');
	if (dotIndex == -1 || dotIndex + 1 == fileName.length()) {
	    // can't find suitable index
	    return null;
	}
	String fileExtension = fileName.substring(dotIndex + 1);

	// open a client socket connection
	Socket socket = null;
	try {
	     socket = new Socket(host, PORT);
	} catch(Exception e) {
	    return null;
	}

	String type = null;
	String subType = null;

	/* 
	 * protocol:
	 * Write: file extension
	 * Read: "null" + '\n' 
         *       type + '\n' + subtype + '\n'
	 */
	try {
	    InputStreamReader inputReader = 
		new InputStreamReader(socket.getInputStream());
	    BufferedReader reader = new BufferedReader(inputReader);
	    OutputStreamWriter outputWriter = 
		new OutputStreamWriter(socket.getOutputStream());
	    BufferedWriter writer = new BufferedWriter(outputWriter);

	    writer.write(fileExtension);
	    writer.newLine();
	    writer.flush();

	    type = reader.readLine();
	    if (type.equals("null")) {
		return null;
	    }
	    subType = reader.readLine();
	} catch(IOException e) {
	    return null;
	}
	// and finally
	return new MIMEType(type, subType);
    }
} // FileClassifierProxy

11.3.2 FileServerImpl

On the server side will be running the service. This will run in its own thread (inheriting from Thread) and will listen for connections. When one is received, it will create a new Connection object also in its own thread to handle the message exchange. (This creation of another thread is probably overkill here where the entire message exchange is very short, but is better practice for more complex situations.)



/**
 * FileServerImpl.java
 */

package socket;

import java.net.*;
import java.io.*;

public class FileServerImpl extends Thread {
    
    protected ServerSocket listenSocket;

    public FileServerImpl() {
	try {
	    listenSocket = new ServerSocket(FileClassifierProxy.PORT);
	} catch(IOException e) {
	    e.printStackTrace();
	}
    }

    public void run() {
	try {
	    while(true) {
		Socket clientSocket = listenSocket.accept();
		new Connection(clientSocket).start();
	    }
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }
} // FileServerImpl

class Connection extends Thread {

    protected Socket client;

    public Connection(Socket clientSocket) {
	client = clientSocket;
    }

    public void run() {
	String contentType = null;
	String subType = null;

	try {
	    InputStreamReader inputReader = 
		new InputStreamReader(client.getInputStream());
	    BufferedReader reader = new BufferedReader(inputReader);
	    OutputStreamWriter outputWriter = 
		new OutputStreamWriter(client.getOutputStream());
	    BufferedWriter writer = new BufferedWriter(outputWriter);

	    String fileExtension = reader.readLine();

	    if (fileExtension.equals("gif")) {
		contentType = "image";
		subType = "gif";
	    } else if (fileExtension.equals("txt")) {
		contentType = "text";
		subType = "plain";
	    } // etc

	    if (contentType == null) {
		writer.write("null");
	    } else {
		writer.write(contentType);
		writer.newLine();
		writer.write(subType);
	    }
	    writer.newLine();
	    writer.close();
	} catch(IOException e) {
	    e.printStackTrace();
	}
    }
}

11.3.3 Server

The Jini server must start a FileServerImpl to listen for later connections. Then it can register a proxy object FileClassifierProxy with each lookup service which will send it on to interested clients. The proxy object must know where the service is listening in order to attempt a connection to it, and this is given by first making a query for the local host and then passing the hostname to the proxy in its constructor.



package socket;

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;  // Jini 1.0
// import com.sun.jini.lease.LeaseListener;        // Jini 1.0
// import com.sun.jini.lease.LeaseRenewalEvent;    // Jini 1.0
import net.jini.lease.LeaseRenewalManager;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import java.rmi.RMISecurityManager;
import java.net.*;

/**
 * FileClassifierServer.java
 */

public class FileClassifierServer implements DiscoveryListener, LeaseListener {

    protected FileClassifierProxy proxy;
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    
    public static void main(String argv[]) {
	new FileClassifierServer();
	try {
	    Thread.sleep(1000000L);
	} catch(Exception e) {
	}
    }

    public FileClassifierServer() {
	try {
	    new FileServerImpl().start();
	} catch(Exception e) {
            System.err.println("New impl: " + e.toString());
            System.exit(1);
	}

	// set RMI scurity manager
	System.setSecurityManager(new RMISecurityManager());

	// proxy primed with address
	String host = null;
	try {
	    host = InetAddress.getLocalHost().getHostName();
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	proxy = new FileClassifierProxy(host);
	// now continue as before
	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++) {
System.out.println("found registrars");
            ServiceRegistrar registrar = registrars[n];

	    // export the proxy service
	    ServiceItem item = new ServiceItem(null,
					       proxy, 
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.print("Register exception: ");
		e.printStackTrace();
		// System.exit(2);
		continue;
	    }
	    try {
		System.out.println("service registered at " +
				   registrar.getLocator().getHost());
	    } catch(Exception e) {
	    }
	    leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }
        
} // FileClassifierServer

11.3.4 What classes need to be where?

We have the classes

  1. common.MIMEType

  2. common.FileClassifier

  3. socket.FileClassifierProxy

  4. socket.FileServerImpl

  5. socket.FileClassifierServer

  6. client.TestFileClassifier

These could be running on upto four different machines
  1. The server machine for FileClassifierServer

  2. The HTTP server, which may be on a different machine

  3. The machine for the lookup service

  4. The machine running the client TestFileClassifier

What classes need to be known to which machines?

The server running FileClassifierServer needs to know the following classes and interfaces

  1. The common.FileClassifier interface

  2. The class common.MIMEType

  3. The class socket.FileClassifierServer

  4. The class socket.FileClassifierProxy

  5. The class socket.FileServerImpl

  6. The class socket.Connection (an class in FileServerImpl.java)

The lookup service does not need to know any of these classes. It just deals with them in the form of a java.rmi.MarshalledObject

The client needs to know

  1. The common.FileClassifier interface

  2. The class common.MIMEType

In addition, the HTTP server needs to be able to load and store classes. It needs to be able to access

  1. The socket.FileClassifierProxy interface

  2. The common.FileClassifier interface

  3. The class common.MIMEType

11.3.5 Running the non-RMI Proxy FileClassifier

Again we have a server and a client to run. Calling the client is unchanged as it does not care which server implementation is used


java -Djava.security.policy=policy.all client.TestFileClassifier

The value of java.rmi.server.codebase must specify the protocol used by the HTTP server to find the class files. This could be the file protocol or the http protocol. If the class files are stored on my Web server's pages under classes/socket/FileClassifierProxy.class. the codebase would be specified as


java.rmi.server.codebase=http://myWebHost/classes/
(where myWebHost is the name of the HTTP's server host).

The server also sets a security manager. This is a restrictive one, so it needs to be told to allow access. This can be done by setting the property java.security.policy to point to a security policy file such as policy.all.

Combining all these points leads to startups such as


java -Djava.rmi.server.codebase=http://myWebHost/classes/ \
     -Djava.security.policy=policy.all \
     FileClassifierServer

An Ant file to build and deploy this project is socket.FileClassifierServer.xml


	  
	

11.4. RMI and non-RMI Proxies for FileClassifier

An alternative that is often used for client-server systems instead of message passing is remote procedure calls (RPC). This involves a client that does some local processing and makes some RPC calls to the server. We can also bring this into the Jini world by using a proxy that does some processing on the client side, and makes use of an RMI proxy/stub when it needs to make calls back to the service.

Some file types are more common than others: gifs, doc files, html files, etc abound. But there are many more types, ranging from less common ones such as FrameMaker MIF files through to downright obscure ones such as PDP11 overlay files. An implementation of a file classifier might place the common types in a proxy object which makes them quickly available to clients, and the less common ones back on the server, accessible through a (slower) RMI call.

11.4.1 FileClassifierProxy

The proxy object will implement FileClassifier in order that clients can find it. The implementation will handle some file types locally, but others it will pass on to another object, that implements the ExtendedFileClassifier interface. The ExtendedFileClassifier has one method, getExtraMIMEType(). The proxy is told about this other object at constructor time. This class is



/**
 * FileClassifierProxy.java
 */

package extended;

import common.FileClassifier;
import common.ExtendedFileClassifier;
import common.MIMEType;
import java.rmi.RemoteException;
import java.rmi.Remote;

public class FileClassifierProxy implements FileClassifier, java.io.Serializable {
    
    /**
     * The service object that knows lots more MIME types
     */
    protected RemoteExtendedFileClassifier extension;

    public FileClassifierProxy(Remote ext) {
	this.extension = (RemoteExtendedFileClassifier) ext;
    }

    public MIMEType getMIMEType(String fileName) 
	throws RemoteException {
            if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else {
	    // we don't know it, pass it on to the service
	    return extension.getExtraMIMEType(fileName);
	}
    }
} // FileClassifierProxy

11.4.2 ExtendedFileClassifier

The ExtendedFileClassifier interface will be the top level interface for the service and an RMI proxy for the service. It will be publically available for all clients to use. An immediate sub-interface, RemoteExtendedFileClassifier will add the Remote interface



/**
 * ExtendedFileClassifier.java
 */

package common;

import java.io.Serializable;
import java.rmi.RemoteException;

public interface ExtendedFileClassifier extends Serializable {
    
    public MIMEType getExtraMIMEType(String fileName)
	throws RemoteException;
    
} // ExtendedFileClassifier

and


/**
 * RemoteExtendedFileClassifier.java
 */

package extended;

import java.rmi.Remote;

interface RemoteExtendedFileClassifier extends common.ExtendedFileClassifier, Remote {

} // RemoteExtendedFileClassifier

11.4.3 ExtendedFileClassifierImpl

The implementation of the ExtendedFileClassifier interface is done by an ExtendedFileClassifierImpl object. Since this object may handle requests from many proxies, an alternative implementation of searching for MIME types that is more efficient for repeated searches is used



/**
 * ExtendedFileClassifierImpl.java
 */

package extended;

import java.rmi.server.UnicastRemoteObject;
import common.MIMEType;
import java.util.HashMap;
import java.util.Map;

public class ExtendedFileClassifierImpl
    implements RemoteExtendedFileClassifier {

    /**
     * Map of String extensions to MIME types
     */
    protected Map map = new HashMap();
    
    public ExtendedFileClassifierImpl() throws java.rmi.RemoteException {
	/* This object will handle all classification attempts
	 * that fail in client-side classifiers. It will be around
	 * a long time, and may be called frequently. So it is worth
	 * optimising the implementation by using a hash map
	 */
	map.put("rtf", new MIMEType("application", "rtf"));
	map.put("dvi", new MIMEType("application", "x-dvi"));
	map.put("png", new MIMEType("image", "png"));
	// etc
    }
    
    public MIMEType getExtraMIMEType(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; 
    }
} // ExtendedFileClassifierImpl

11.4.4 FileClassifierServer

The final piece in this jigsaw is the server that creates the service (and implicitly the RMI proxy for the service) and also the proxy primed with knowledge of the service.



package extended;

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 net.jini.lease.LeaseRenewalManager;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import java.rmi.RMISecurityManager;
import java.rmi.Remote;

import net.jini.config.*; 
import net.jini.export.*; 

import rmi.RemoteFileClassifier;

/**
 * FileClassifierServer.java
 */

public class FileClassifierServer implements DiscoveryListener, LeaseListener {

    protected FileClassifierProxy smartProxy;
    protected Remote rmiProxy;
    protected ExtendedFileClassifierImpl impl;
    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    private static String CONFIG_FILE = "jeri/file_classifier_server.config";
    
    public static void main(String argv[]) {
	new FileClassifierServer();
	// RMI keeps this alive
    }

    public FileClassifierServer() {
	try {
	    impl = new ExtendedFileClassifierImpl();
	} catch(Exception e) {
            System.err.println("New impl: " + e.toString());
            System.exit(1);
	}

	String[] configArgs = new String[] {CONFIG_FILE};

	try {
	    // get the configuration (by default a FileConfiguration) 
	    Configuration config = ConfigurationProvider.getInstance(configArgs); 
	    
	    // and use this to construct an exporter
	    Exporter exporter = (Exporter) config.getEntry( "FileClassifierServer", 
							    "exporter", 
							    Exporter.class); 
	    // export an object of this class
	    rmiProxy = (RemoteFileClassifier) exporter.export(impl);
	} catch(Exception e) {
	    System.err.println(e.toString());
	    e.printStackTrace();
	    System.exit(1);
	}


	// set RMI scurity manager
	System.setSecurityManager(new RMISecurityManager());

	// proxy primed with impl
	smartProxy = new FileClassifierProxy(rmiProxy);

	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++) {
System.out.println("found registrars");
            ServiceRegistrar registrar = registrars[n];

	    // export the proxy service
	    ServiceItem item = new ServiceItem(null,
					       smartProxy, 
					       null);
	    ServiceRegistration reg = null;
	    try {
		reg = registrar.register(item, Lease.FOREVER);
	    } catch(java.rmi.RemoteException e) {
		System.err.print("Register exception: ");
		e.printStackTrace();
		continue;
	    }
	    try {
		System.out.println("service registered at " +
				   registrar.getLocator().getHost());
	    } catch(Exception e) {
	    }
	    leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
	}
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }
        
} // FileClassifierServer

11.4.5 What classes need to be where?

We have the classes

  1. common.MIMEType

  2. common.FileClassifier

  3. common.ExtendedFileClassifier

  4. rmi.RemoteFileClassifier

  5. extended.FileClassifierProxy

  6. extended.RemoteExtendedFileClassifier

  7. The class extended.ExtendedFileClassifierImpl

  8. extended.ExtendedFileServerImpl

  9. extended.FileClassifierServer

  10. client.TestFileClassifier

The server running FileClassifierServer needs to know the following classes and interfaces

  1. The common.FileClassifier interface

  2. The class common.MIMEType

  3. common.ExtendedFileClassifier

  4. rmi.RemoteFileClassifier

  5. The class extended.FileClassifierServer

  6. The class extended.FileClassifierProxy

  7. extended.RemoteExtendedFileClassifier

  8. The class extended.ExtendedFileClassifierImpl

  9. The class extended.ExtendedFileServerImpl

The lookup service does not need to know any of these classes. It just deals with them in the form of a java.rmi.MarshalledObject

The client needs to know

  1. The common.FileClassifier interface

  2. The class common.MIMEType

In addition, the HTTP server needs to be able to load and store classes. This HTTP codebase must have all the files related to an exported object except for those classes which the client already has (they would be redundant). So it needs to be able to access

  1. rmi.RemoteFileClassifier

  2. The common.ExtendedFileClassifier interface

  3. The extended.FileClassifierProxy interface

  4. extended.RemoteExtendedFileClassifier

An Ant file to build and deploy these is extended.FileClassifierServer.xml



11.5. Using Other Services

In 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 heart beart live to the Web: a heart monitor was attached to him, which talked on an RS232 link to a mobile phone he was carrying. This did a data transfer to a program running at 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 this 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

  1. Have a service which we can locate using the service type ("display a heart monitor trace") and information about it such as whose heart trace it is showing

  2. The service can connect to an http address encoded into the service by its constructor (or other means), read from this and display the contents read assuming it is heart cardiograph data

  3. The information about whose trace it is can be given by a Name entry

The client shows as the screen dump in figure 11.9.
Figure 11.9: Heart monitor trace

The heart monitor service can be regarded in a number of ways:

  1. It is a full-blown service uploaded to the client, that just happens to use an external data source supplied from an HTTP server

  2. It is a "fat" proxy to the HTTP service, which acts as a client to this service by displaying the data

Many other non-RMI services can be built that act in this "fat proxy" style.

11.5.1 Heart interface

The 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

11.5.2 Heart server

The HeartServer is similar to the "option 2" method, where it exports a complete service. This service, of type HeartImpl is primed with a URL of where the heart data is stored. This data will later be delivered by an HTTP server. This implementation is enough to locate the service. However, rather than just anyone's heart data, a client may wish to search for a particular persons data. This can be done by adding a Name entry as additional information to the service.


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 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("file:/home/jan/projects/jini/doc/heart/TECG3.ecg"),
					       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

11.5.3 Heart client

The client searches for a service implementing the Heart interface, with the additional requirement that it be for a particular person. Once it has this, it just calls the method show() 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


11.5.4 Heart implementation

The HeartImpl class opens a connection to an HTTP server and requests delivery of a file. For heart data it needs to display this at a reasonable rate, so it reads, draws, 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 customises 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: --";
	}
	// ? ask for repaint? 
    }

    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
		    // conver 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
	    }
	}
    }
}

11.6. Copyright

If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone . The current edition of the book does not yet deal with Jini 2.0, but the next edition will.

This file is Copyright (©) 1999, 2000, 2001, 2003, 2004 by Jan Newmarch (http://jan.netcomp.edu.au) jan@newmarch.name.

Creative Commons License This work is licensed under a Creative Commons License, the replacement for the earlier Open Content License.