The RemoteCallback Pattern

Intent

Use RMI for callbacks with a variety of different client classes, without exporting them to the server, or trusting them on the server.

Motivation

Some systems use callbacks to transfer control information from client to server, without wanting to execute code on the server. Client code may change much more frequently than server code, and the server might not wish to run any bytecodes provided by client-controlled web servers.

This pattern allows client code to receive callbacks from the server via a fixed wrapper class that would be as stable as the interface class. Client side code to be called back need not implement Remote, have rmic run on it, or ever leave the client. Each client could use different callback classes.

The server is protected from client provided bytecodes, because there aren't any. The worst a misbehaving client can do is to tie up a thread of control by hanging in the remote call, or throw an exception. It cannot use any unexcepted amount of server side cpu or memory (per call).

This pattern is detailed enough to be almost completely automated using code generation. A code generator for it is provided.

Applicability

Use the RemoteCallback pattern when

Structure

This is a specialisation of Proxy to the circumstances of Java RMI. Recall that objects implementing Remote are passed over RMI by references (strictly speaking, by serializing Stub classes in their place), whilst other objects are passed in serialized form. We split the callback interface into two interfaces. One interface, Callback in my example code, declares that all methods throw RemoteException, but does not extend Remote. This interface is the one implemented by client classes to be called back. Since it does not implement Remote, there is no point using rmic on these classes. If these classes were to pass over RMI, they would have to go in serialized form, but in fact they need never be transferred from server to client or vice versa.

The second interface, RemoteCallback in my example code, inherits from Callback, and also from Remote. This is why the methods in the first interface need to declare RemoteException: this interface must, and it must also inherit from the first interface. RemoteCallback need be implemented by only one class, which I will call CallbackWrapper. This contains a single private member of the type of the first interface. It works by delegating all interface methods to this member. CallbackWrapper can, and probably should, be final. Any classes produced by subclassing it would need their own stubs, and so would (and probably should) fail at run time. We can prevent this at compile time by prohibiting subclasses via final.

Since both RemoteCallback and CallbackWrapper are completely predictable from the first interface, they are as stable as it is, and could be automatically produced from it. Sun have pointed out that it is quite easy to understand much of the structure of a .java file be plugging a java class extending Doclet into javadoc. Also included here is a java doclet, CallbackGenDoclet.java, which does just this.

Client code registers a callback by constructing an object of any class (say Client) implementing Callback, and then passing it to the constructor of the CallbackWrapper class. This provides the client with an object of a class, CallbackWrapper, already known to both server and client. The client can pass this CallbackWrapper object to the server, by making whatever RMI call the server has defined. This involves transferring only a serialized CallbackWrapper_stub, bytecodes for which could already be in the server's CLASSPATH. When the server makes the callback, only the stub code inside CallbackWrapper_Stub will be executed on the server. This code will immediately make an RMI call back to the client, where the CallbackWrapper code will delegate to a method in the client class implementing Callback.

Consequences

This deliberately provides only a subset of the possibilities of RMI. It is designed to stop the server dynamically loading code from the client, which means that it cannot take cpu load off the client, or provide flexible access to resources (e.g. IO or memory), by hosting client code.

Implementation

Is described in the accompanying code. If the server really does not trust the client code it will probably have to catch exceptions thrown by client callback routines (including e.g. NullPointerException, which will be helpfully serialized by RMI when it occurs, transferred over to the server side, and thrown there). It will probably also want to devote a separate thread to each client, and use some form of timeout mechanism, to stop misbehaving clients delaying service to others, or tying up server threads, by hanging in the callback. This is not obvious from the example code, because the example callback is immediate and happens in an RMI service thread, which is probably not likely in real life.

Can this be done via some lesser-known RMI facility? I don't know, but I wouldn't be surprised if it could be done by overriding some of the methods in UnicastRemoteObject. In practice, I suspect that this is done a lot by providing the server with stub classes generated from client classes that are many versions behind the classes actually running on the client, and observing that this appears to work.

Related Patterns

Example

RemoteCallback.zip is a jar file containing demonstration Java code showing a client and server using this pattern (in fact this code uses the pattern in both directions). It also contains batch files build.bat and run.bat to build and run this test, and a copy of this html file. The batch files buildCodeGen.bat and runCodeGen.bat do the same thing, but using auto-generation via javadoc. Unfortunately this means that you will have to edit buildCodeGen.bat to fill in the location of tools.jar on your machine.