Skip to main content

Simple Inter-JVM communication... The Grail!

Posted by cajo on September 3, 2007 at 4:19 PM PDT

I am very pleased to announce a most significant breakthrough from the the cajo project, in the ease with which distributed computing can be accomplished in Java; and in only 20 kilobytes. It works with all JREs, 1.3 and later. (And before you Rocket Scientists out there ask; yes, it's also 64-bit clean ;)

Just three methods: (click the link, for greater detail)

void export(Object object);

This makes any object's public methods remotely callable


Object[] lookup(Class methodSet);

This finds remote objects matching the given interface


Object proxy(Object reference, Class methodSet);

This creates a local proxy for using the remote object

That's it. These allow for complete interoperability between distributed JVMs. It just can't get any easier than this.

Any object can be exported for remote object invocation, irrespective of the classes it implements, or interfaces it extends. An exported object can be used by a client, simply if it is compatible with the signature requirements specified by the client. Argument and return type coërcion is applied, in compliance with the language specification.

Comfortably, the exported methods can be used with the same syntax and language conventions as local methods. However, it is always well to keep in mind the Eight Fallacies of Distributed Computing. [pdf warning]

Strictly speaking the simple 3 method Grail interface could be implemented by other distributed frameworks; Jini, EJB, Spring, etc. So I've placed the interface specification into the public domain: To leave that as a bit of an open challenge! ;-)

OK, time for an example: Server.java

import gnu.cajo.Cajo; // The cajo implementation of the Grail

import gnu.cajo.Cajo;

public class Server {

   public static class Test { // remotely callable classes must be public
      // though not necessarily declared in the same class
      private final String greeting;
      // no silly requirement to have no-arg constructors
      public Test(String greeting) { this.greeting = greeting; }
      // all public methods, instance or static, will be remotely callable
      public String foo(Object bar, int count) {
         System.out.println("foo called w/ " + bar + ' ' + count + " count");
         return greeting;
      }
      public Boolean bar(int count) {
         System.out.println("bar called w/ " + count + " count");
         return Boolean.TRUE;
      }
      public boolean baz() {
         System.out.println("baz called");
         return true;
      }
      public String other() { // functionality not needed by the test client
         return "This is extra stuff";
      }
   } // arguments and return objects can be custom or common to server and client

   public static void main(String args[]) throws Exception { // unit test
      Cajo cajo = new Cajo(1198, null, null);
      System.out.println("Server running");
      cajo.export(new Test("Thanks"));
   }
}

Compile via: javac -cp grail.jar;. Server.java

Execute via: java -cp grail.jar;. Server

Now for a client: Client.java

import gnu.cajo.Cajo;

import java.rmi.RemoteException; // caused by network related errors

interface SuperSet {  // client method sets need not be public
   void baz() throws RemoteException;
} // declaring RemoteException is optional, but a nice reminder

interface ClientSet extends SuperSet {
   boolean bar(Integer quantum) throws RemoteException;
   Object foo(String barbaz, int foobar) throws RemoteException;
} // the order of the client method set does not matter

public class Client {
   public static void main(String args[]) throws Exception { // unit test
      Cajo cajo = new Cajo(0, null, null);
      if (args.length > 0) { // either approach must work...
         int port = args.length > 1 ? Integer.parseInt(args[1]) : 1198;
         cajo.register(args[0], port);
         // find server by registry address & port, or...
      } else Thread.currentThread().sleep(100); // allow some discovery time

      Object refs[] = cajo.lookup(ClientSet.class);
      if (refs.length > 0) { // compatible server objects found
         System.out.println("Found " + refs.length);
         ClientSet cs = (ClientSet)cajo.proxy(refs[0], ClientSet.class);
         cs.baz();
         System.out.println(cs.bar(new Integer(77)));
         System.out.println(cs.foo(null, 99));
      } else System.out.println("No server objects found");
      System.exit(0); // nothing else left to do, so we can shut down
   }
}

Compile via: javac -cp grail.jar;. Client.java

Execute via: java -cp grail.jar;. Client

That's all there is to it. Detailed information, including source code, can be found here, also the main project site is a very helpful resource.

Enjoy, and as always, your comments and feedback are encouraged.

-John

Related Topics >>