the cajo project
Free, simple, powerful: Transparent Distributed Computing
Adding cajo to existing applications
It is very easy to incorporate the cajo framework into applications. It simply makes your object's public methods, both instance and static, remotely callable. As you will see very shortly; making an object in your application remotely accessible, or obtaining a reference to an object in a remote application, can be done with as little as one additional line of code!

Consider for example, the following simple object:
public class SomeObject { // the class need not be public
   protected String string;
   protected int stuff;
   public String foo() { return string; }
   public void bar(String string, int stuff) {
      this.string = string;
      this.stuff = stuff;
   }
   public String baz(String string) {
      try { return this.string; }
      finally { this.string = string; } // fun (:
   }
}
Remote binding and linking
One of the most important aspects of the cajo framework is that it does not require any changes to your objects, in order to use them remotely. Even if your application already uses some other distributed framework; it is very easy to 'drop-in' cajo support, to compliment and extend your distributed computing capability, and to increase your distributed user base.

To make an object remotely accessible, the framework provides two distinct options; these are known collectively as static, and dynamic binding. Both may even be used on the same object.

Static binding is accomplished by calling the static bind method, of the ItemServer class. This type of binding will require the client to know the address of the server, in order to obtain a reference to the object. Clients statically link to a remote object, using the static getItem method of the Remote class.

Static binding and linking would look something like this:
ItemServer.bind(someObject, "someName"); // server side

Object someObject = Remote.getItem("//serverName:serverPort/someName"); // client side
Dynamic binding is accomplished by calling the announce method, of the Multicast class. This will 'broadcast' the remote object reference to all listening Virtual Machines. Clients dynamically link to a remote object, using the listen method of the Multicast class.

Dynamic binding and linking would look something like this:
Multicast.announce(someObject, 16); // server side

Multicast.listen(someListener); // client side
In either case, once a remote reference to an object is obtained, it can be used to invoke its public methods. The reference can also be passed to other objects, including remote ones, as either an argument, or a return. It could even be thrown in an Exception. Any object receiving this reference, can also invoke its public methods. The reference can even be written to a file, using the static zedmob method of the Remote class, for later use, even by a different program. Note however, the exposed object's public methods are not synchronised, meaning that they can be invoked re-entrantly by clients. Just as with local objects; synchronising methods can cause significant client performance impacts. It is best to synchronise only the smallest possible blocks of code that require protection to be threadsafe.

Consider for a moment the useful network resource created simply by a single-line of code remoting a Hashtable for instance.
Dynamic method invocation
Invoke is the cajo architecture approach to dynamic method invocation, supplemented by the use of Java reflection. It is the foundation of the framework. It can be used to invoke any public method, including static, on any object; on both local and remote!

The static method has the following signature:
Object Remote.invoke(Object object, String method, Object args) throws Exception;
The object argument is a reference to either a local or remote object, on which to operate. The method argument is the name of the public method to be invoked. The args argument represents the argument(s) to be provided to the method, if any. Multiple arguments are represented when args is an Object array. A null args value, or less preferably an empty array, indicates no arguments. The method's result is the object returned from the method invocation, if any. The method declares that it throws Exception, to allow the called method the freedom to throw any type of Exception, including none at all. This is also used to cover network related failures for invocations on remote objects.

Consider for a moment just this one small example of the power in dynamic method invocation:
A GUI could allow the user to select arguments from a collection, and type in the name of the host, the object, and the method; then view the results. All at runtime!
Type safety
Now it might appear that all typesafety has been completely abandoned; looking a bit like those old, currently fashionably called, 'dynamic languages', like BASIC and Ruby. However, what has really happened is that strong typechecking had to move, from compile time to run time. If there is no strongly mappable signature, the invocation will result in a java.lang.NoSuchMethodException.

However, there is an extremely useful technique, to restore the safety of strong compile time typechecking; it is called a Wrapper class.

A client can easily define a wrapper class, to provide a compile time typesafe interface to the remote object reference.

For example, to generically wrap our previous example:
import java.rmi.RemoteException;
import gnu.cajo.invoke.Remote;

public class WrapperObject {
   protected Object obj;
   public WrapperObject() {
      // obj = new SomeObject(); // when local, or...

      try { obj = Remote.getItem("//serverName:serverPort/someName"); }
      catch(RemoteException rx) { /* network exception */ }
      catch(Exception x) { /* instantiation exception */ }
   }
   public String foo() {
      try { return (String)Remote.invoke(obj, "foo", null); }
      catch(RemoteException rx) { /* network exception */ }
      catch(Exception x) { /* method exception */ }
   }
   public void bar(String string, int stuff) {
      try { Remote.invoke(obj, "bar", new Object[] { string, stuff }); }
      catch(RemoteException rx) { /* network exception */ }
      catch(Exception x) { /* method exception */ }
   }
   public String baz(String string) {
      try { return (String)Remote.invoke(obj, "baz", string); }
      catch(RemoteException rx) { /* network exception */ }
      catch(Exception x) { /* method exception */ }
   }
}
Note the network exception catch blocks: Any remote invocation can fail unexpectedly, in a way that would not occur if the wrapped object reference is local. The wrapper architecture also facilitates the simple insertion of extremely powerful supplemental functionality such as: retry, caching, monitoring, notification, logging, measurement, debug, testing, validation, rollback, load balancing, fail-over, etc.

Note how this object interface is not a Java interface, nor is the remote reference to a Java interface; to pre-empt any confusion of terminology, we will refer to this practice via the term: Client Subtype. The remote object is free to add new methods, for newer clients, but simply in a manner compatible with the existing clients.

It is important to remember that since the Remote.invoke method works on both local and remote objects; the wrapper technique provides a client the ability to transparently scale, from local object interaction, to remote; without affecting the remaining source code. It also provides the flexibility to change its wrapped object from local, to remote, or to a different server; even at runtime. It similarly allows the server the flexibility to return a remote reference, or a locally running proxy, or a completely local object; all without affecting the client's remaining code!

There is one more thing to consider: While the wrapper approach is highly recommended; If you prefer complete transparency, and not to have to deal with checked exceptions, there is an alternate approach supported by the project, to make remote objects behave identically as if they were local.
Conclusion
The diagramme below illustrates the options for receiving and exposing object references from a Virtual Machine:

cajo object linking
Given the transparency of this framework, you will quickly find it so easy to distribute your applications; you will practically forget the framework is even there!

All of this functionality; and quite a lot more: From a codebase of 40kB!

Its simplicity, flexibility, ease of use, and sheer performance; offer quite an exciting change from the more complex, and heavyweight distributed frameworks out there.

If you are are interested to try out the example we have discussed here, please visit our getting started page.

The cajo framework does not dictate your architecture: It simply drops-in!
© 2004 John Catherino, GNU FDL