Wednesday, September 9, 2009

RMI client callbacks

In a project that I have worked for in the past, the RMI clients could call asynchronous methods on the RMI server. After making these calls the client could return to whatever it is doing. The RMI server would run the method/tasks on seperate threads which would take a long time to complete. Once finished the RMI server would send back the reponse to the RMIClient. In order to send the response, the RMI server needs to make remote calls to its clients. So the RMI client has to act like a RMI server. To accomplish this, a RMI client must be a remote object and it must be able to accept incoming calls from others. This is achieved on the RMI client by calling the following static method:


UnicastRemoteObject.exportObject (remote_object)

The exportObject() method returns a stub which can be cast to the remote interface that the client implements. Here is an example which simulates this.

I have the following pieces of code:

  • AttractionListener.java - The remote interface which the client will implement. It has a callback method attractionAdded() which the RMI server will invoke on completion of a long running task.
  • CallbackClient.java - The client code which invokes the long running method addAttraction() on the RMI server.
  • Attractions.java - The remote interface which will be implemented by the RMI Server. It has a method addAttractionListener() which lets the client register itself with the server.
  • AttractionsImpl.java - The RMI server which implements Attractions interface. It runs the long running task on a seperate thread.
  • ServerRMI.java - The code which binds the RMI server to the rmi registry.
AttractionListener .java


import java.rmi.Remote;
import java.rmi.RemoteException;

public interface AttractionListener extends Remote {
public void attractionAdded(String attraction) throws RemoteException;
}


CallbackClient.java


import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Scanner;


public class CallbackClient implements AttractionListener {


public void attractionAdded(String attraction)
throws RemoteException {
System.out.println("Attraction Added:" + attraction);
}

public static void main(String args[]) {
try {
AttractionListener client = new CallbackClient();
System.out.println("Exporting the client");
UnicastRemoteObject.exportObject(client);
String serverURL = "rmi://127.0.0.1:1099/ServerRMI";
Attractions server =
(Attractions)Naming.lookup(serverURL);
server.addAttractionListener(client);
String s;
while (true) {
System.out.println("Enter new attraction:");
Scanner sc = new Scanner(System.in);
s = sc.next();
if (s != null) {
server.addAttraction(s);
}
}

} catch (Exception e) {
System.out.println("Exception occured " +
"while adding attraction: " +
"\n" + e.getMessage());
}
}
}



Attractions.java

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;


public interface Attractions extends Remote {
public void addAttraction(String name) throws RemoteException;
public List<String> showAttractions() throws RemoteException;
public void addAttractionListener(AttractionListener listener) throws RemoteException;
}



AttractionsImpl.java

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;


public class AttractionsImpl extends UnicastRemoteObject implements Attractions {

private static final long serialVersionUID = 1L;
private List<String> attractions = new ArrayList<String>();
private AttractionListener listener;

protected AttractionsImpl() throws RemoteException {
super();
}

@Override
public void addAttraction(String name) throws RemoteException {
if (name != null) {
verifyAttraction(name);
}
}

@Override
public List<String> showAttractions() throws RemoteException {
List<String> a = new ArrayList<String>();
a.addAll(attractions);
return attractions;
}

@Override
public void addAttractionListener(
AttractionListener listener) throws RemoteException {
this.listener = listener;
}

private void verifyAttraction(String name) {
Runnable t = new VerifyThread(name);
new Thread(t).start();
System.out.println("Verify Thread started");
}

private class VerifyThread implements Runnable {

private String name;

VerifyThread(String name) {
this.name = name;
}

@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("Finished verifying");
attractions.add(name);
if (listener!= null)
listener.attractionAdded(name);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}

}


}

}



ServerRMI.java


import java.rmi.Naming;


public class ServerRMI {

public static void main (String args[] ) {
try {
Attractions attractions = new AttractionsImpl();
Naming.rebind("ServerRMI", attractions);
System.out.println("Server started!");
}
catch (Exception e) {
System.out.println("Exception: " + e);
}
}

}




Here are steps for getting the code working:

Compile the code
run rmic on the CallbackClient
start the rmiregistry
start the server
start the client

Here is a sample output:

Running the client:
Exporting the client
Enter new attraction:
tt
Enter new attraction:
rr
Attraction Added:tt
Attraction Added:rr

Server Output:
Server started!
Verify Thread started
Finished verifying
Verify Thread started
Finished verifying

I have kept the code simple to illustrate RMI callbacks. Usually the server has a List of listeners as there will be multiple clients. Also since RMI server handles each request in separate thread for calls originating from different connections, you will need to synchronize the code i.e. make the server-side code thread-safe.

4 comments:

  1. Hello.
    Thank You for this example, simple and interesting. As I read Java 1.5 creates dynamic stubs (rmic is not necessary). Which changes in code are necessary to do it (UnicastRemoteObject.exportObject needs static stub)?

    ReplyDelete
  2. Hello pb2000,

    Change the line UnicastRemoteObject.exportObject(client); to UnicastRemoteObject.exportObject(client, 0);

    The latter returns Remote while the first one returns RemoteStub. Hope this helps

    ReplyDelete