|
 |
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!
|
 |
|
 |
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.
|
 |
|
 |
The diagramme below illustrates the options for receiving and exposing object
references from a Virtual Machine:
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!
|
|