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.

Thursday, September 3, 2009

InetAddress and Factory Design Pattern

I was recently working on a project which required me to check if a server is reachable or not. I started looking at java.net.InetAddress.isReachable(int) to solve my problem. The javadoc was not very clear and so I went in to see how it was actually implemented. In the java file I came across Factory design pattern for creating Internet address(InetAddress), so I decided to blog about it.

One cannot create the InetAddress objects by invoking the constructor. One has to use static methods like getLocalHost() etc. Based on the type of IP address of the host i.e. whether it is version4 or version6 IP address, a Inet4Address or Inet6Address is returned by the factory. Both these classes extend InetAddress. This is transparent to the client, so the client need not worry about which type of InetAddress to create.

In brief, the factory design pattern delegates the responsibility of creating the correct object to the factory. The factory makes the decision of which class of object to construct based on the user input or some other implicit criteria(here it was IP address type).

Here is the snippet from the code in InetAddress.java which uses Factory Design Pattern to acheive it. In this code the factory is InetAddressImplFactory which makes the decision to either load(call constructor of) the Inet4Address class or Inet6Address class.

public class InetAddress implements java.io.Serializable {

............
static {

...............
impl = (new InetAddressImplFactory()).create();
...........
}

.........
}


class InetAddressImplFactory {

//create v6 or v4 address
static InetAddressImpl create() {
Object o;
if (isIPv6Supported()) {
o = InetAddress.loadImpl("Inet6AddressImpl");
} else {
o = InetAddress.loadImpl("Inet4AddressImpl");
}
return (InetAddressImpl)o;
}

............

}