Declarative RMI

Tutorial

Declarative RMI Tutorial

This section is intended to provide a step-by-step walk through of how to transform a simple RMI application to use a declarative approach. Though the code is replicated in this tutorial, you can also download the tutorial source files here.

Step 1 - Download the library

Before starting, head over to the Download section and grab the DRMI library. The library contains both source and class files, so if you're more adventurous than others please feel free to take a look at the implementation. A word of caution though: this is a research project and the source code has not been refactored to production quality.

Step 2 - Install the library

Installing the library is simple. Just unjar the file into your desired project directory. Note that if you try to run things by simply putting the jar on the classpath, you'll likely get an error when you try to run the modified RMI program.

Step 3 - Create the parameter class

We first want to create a simple class to use as a parameter type. The class, A, will contain a single integer value and a method, bar, to increment that value. The code for the parameter class is as follows:

package tutorial;

import java.io.Serializable;

public class A implements Serializable {
    private static final long serialVersionUID = -1599290009274173019L;
    public A() {}
    public A(int value){ this.value = value; }


    private int value = 0;

    public void bar() { value++; }

    public int getValue() { return value; }
}

Note that this class contains no Java RMI information, and we are not going to define a Remote interface or anything.

Step 4 - Create a Server

In order to keep things simple, our remote server will have only one remote method, foo. This method will take three separate objects all of type A, but it will declare each parameter passed using a different passing semantics. The code for the server is as follows:

package tutorial;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import adaptive.annotations.By;
import adaptive.annotations.PassBy;
import adaptive.annotations.ReturnBy;

public interface ServerInterface extends Remote {

    @PassBy(params={By.Copy, By.RemoteReference, By.CopyRestore})
    public void foo(A param1, A param2, A param3) throws RemoteException;
}

public class Server extends java.rmi.server.UnicastRemoteObject implements ServerInterface{

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println ("Usage: java BServer url");
            return;
        }

        Server server = new Server();
        java.rmi.Naming.bind (args[0], server);
        System.out.println ("bound server at " + args[0]);
    }

    public Server() throws RemoteException
    {

    }

    @PassBy(params={By.Copy, By.RemoteReference, By.CopyRestore})
    public void foo(A param1, A param2, A param3) throws RemoteException
    {
        param1.bar();
        param2.bar();
        param3.bar();
    }
}

The foo method simply calls bar on all its parameters. Note that since bar will increment the value field of A, it will change the client-side instance if it is passed by remote-reference or by copy-restore, but not if it's passed by copy. Since A is not remote (and copy-restore is not built into RMI), by default the standard RMI runtime would pass all three parameters by copy.

Step 5 - Create the client

We have a server with a remote method and a simple parameter class, now all we need is a client which can test that the server performs as expected. The Client class will create three instances of A, print their initial values of 0, call foo on them and then print their new values. If it works, the remote-reference and copy-restore parameters should be changed while the copy parameter should be the same on the client side. The code for the client is as follows:

public class Client {

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Client url");
            return;
        }

        ServerInterface server = null;

        try {
            server = (ServerInterface) java.rmi.Naming.lookup(args[0]);
        } catch (java.rmi.NotBoundException e) {
            e.printStackTrace();
            return;
        } catch (java.net.MalformedURLException e1) {
            e1.printStackTrace();
            return;
        } catch (java.rmi.RemoteException e2) {
            e2.printStackTrace();
            return;
        }

        try {

        //We first create 3 objects to be passed to the server method.
        //Each will be passed by a different semantics.
        A byCopy = new A();
        A byRemoteReference = new A();
        A byCopyRestore = new A();

        //Print off the initial values, before the objects have been passed
        //to the server. The values should all be 0.
        System.out.println("byCopy: initial value=" + byCopy.getValue());
        System.out.println("byRemoteReference: initial value=" + byRemoteReference.getValue());
        System.out.println("byCopyRestore: initial value=" + byCopyRestore.getValue());
        System.out.println();

        //Next, we call the server method, passing it our three objects.
        server.foo(byCopy, byRemoteReference, byCopyRestore);

        //Print off the resulting values, after the objects have been passed
        //to the server. The byCopy object's value should still be 0, as it
        //was passed by copy. However, the byRemoteReference and byCopyRestore
        //objects should have been updated on the server, and their new values
        //should be 1.
        System.out.println("byCopy: new value=" + byCopy.getValue());
        System.out.println("byRemoteReference: new value=" + byRemoteReference.getValue());
        System.out.println("byCopyRestore: new value=" + byCopyRestore.getValue());
        System.out.println();

        } catch (java.rmi.RemoteException e) {
            e.printStackTrace();
        }
    }
}

Step 6 - Generate Server_Stub with rmic

Declarative RMI requires that the servers have a stub class generated via rmic rather than using dynamic proxies. To create the stub, run the following command:

rmic tutorial.Server

This will generate a tutorial.Server_Stub class file which will be modified in the next step.

Step 7 - Run the annotations processor

Up until now, all of the steps were fairly standard procedure for creating an RMI application. Now the cool part comes into play: transforming standard Java RMI into declarative Java RMI. This process will take the tutorial application, process the annotations, morph the bytecode, and generate proxies and remote interfaces.

Conveniently, this only requires one command (executed from the directory above the tutorial package):

javac -proc:only -processor adaptive.annotations.AdaptiveAnnotationProcessor -cp .;bcel-5.0.jar ./tutorial/*.java

If you don't like to type quite this much, you can feel free to use the batch file provided in the tutorial files here. Just execute run_annotations.bat and pass it ./tutorial/*.java as a parameter.

Step 8 - Run the server

Now that you've transformed your application, all that's left is to run it, starting with the server. To start the server, execute the following commands:

start rmiregistry
java -cp . tutorial.Server //localhost/server

Again, you could also use the tutorial_server batch file provided here. If you get an error when launching the server, see our FAQ.

Step 9 - Run the client

The moment of truth! All that's left now is to run the client and make sure it gives us the expected output. To start the client, execute the following command (or use tutorial_client.bat provided here):

java tutorial.Client //localhost/server

The output should look something like this:

(output here)

Conclusions

That's it folks! Once you have an annotated RMI application, it's as simple as just invoking the annotations processor and running the application.