A Jini service will be implemented using a proxy on the client side and a service backend on the service provider side. In RPC-like systems there is little choice: the proxy must be thin and the backend must be fat. Message-based client/server systems allow choices in the distribution of processing, so that one or other side can be fat or thin, or they can equally share. Jini allows a similar range of choices, but does so using the object-oriented paradigm supported by Java. The following sections discuss the choices in detail, giving alternative implementations of a file-classifier service.
Proxy Is the Service
One extreme proxy situation 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 9-1. This was the implementation discussed in the previous chapter.
Figure 9-1: Class diagram for file classifier
We have already seen the full object diagram for the JVMs in Chapter 8, but just concentrating on these classes looks like Figure 9-2.
Figure 9-2: Objects in the JVMs
The client asks for a FileClassifier . What is uploaded to the service locators, and thus 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 if the service is purely a software one that does not need any link back to the server. It could be something like a calendar that is independent of location, or a diary that uses files on the client side rather than the server side.
The opposite proxy extreme 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 9.3.
Figure 9-3: Class diagram for RMI proxy
The objects in the JVMs are shown in Figure 9-4.
Figure 9-4: JVM objects for RMI proxy
The full code for this mechanism is given later in the chapter in the "RMI Proxy for FileClassifier" section.
The class structure for this mechanism is much more complex than the fat proxy because of RMI requirements. The RemoteFileClassifier interface has to be defined, and the implementation class has to implement (or call suitable methods from) the UnicastRemoteObject class. The FileClassifierImpl_Stub is generated from FileClassifierImpl by using the rmic compiler. Implementing the Remote interface allows the methods of the FileClassifierImpl to be called remotely. Inheriting from UnicastRemoteObject allows RMI to export the stub rather than the service, which remains on the server.
Apart from creating the stub class by using rmic , the stub is essentially invisible to the programmer; the server code is written to export the implementation, but the RMI runtime component of Java recognizes this and actually exports the stub instead. This can cause a little confusion ”the programmer writes code to export an object of one class, but an object of a different class appears in the service locator and in the client.
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 give further examples.
If RMI is not used, and the proxy and backend service want to share processing, then both the backend service and the proxy must be created explicitly on the service provider side. The proxy is explicitly exported by the service provider and must implement the interface, but on the server side this requirement does not hold, since the proxy and backend service are not tightly linked by a class structure any more. The class diagram for the file classifier with this organization is displayed in Figure 9-5.
Figure 9-5: Class diagram for non-RMI proxy
The JVMs at runtime for this scenario are shown in Figure 9-6.
Figure 9-6: JVM objects for a 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 that 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 later in this chapter in the "Non-RMI Proxy for FileClassifier" section.
This model is good for bringing "legacy" client/server applications into the Jini world. Client/server applications often communicate using a specialized protocol between the client and server. Copies of the client have to be distributed to all machines, and 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 protocol, 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, that have varying degrees of support for HTML 3.2 and HTML 4.0 features, let alone new protocol extensions such as style sheets 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 Jini proxy is the real "heart" of the client, whereas the Jini backend service is the server part of the original client/server system. When changes occur, the backend 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.
RMI and Non-RMI Proxies
The last variation is to have a backend service, an explicit proxy, and an RMI proxy. Both of the proxies are exported: the explicit proxy has to be exported by registering it with lookup services, while the RMI proxy is exported by the RMI runtime mechanisms. The RMI proxy can be used as an intermediary for RPC-like communication between the explicit proxy and the backend service. This is just like the last case, but instead of requiring the proxy and service to implement their own communication protocol, it uses RMI instead. The proxy and service can be of any relative size , just like in the last case. What this does is simplify the task of the programmer.
Later in the chapter, in the "RMI and Non-RMI Proxies for FileClassifier" section, there is a non-RMI proxy, FileClassifierProxy , implementing the FileClassifier interface. This communicates with an object that 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 9-7.
Figure 9-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 JVMs are displayed in Figure 9-8.
Figure 9-8: JVM objects for RMI and non-RMI proxies