The key parts of an RMI implementation are the following:
1. Serving Remote Stubs: As I glossily put it above, the client code must get the stub class somehow. Prosaically, we can just publish the stub on the web somewhere and tell interested users to do an ftp or http download of the stub in order to user our RMI services. But this is Java, so as you should expect, there is also a way to dynamically get the stubs to the clients. This is done on the server side by setting the system property java.rmi.server.codebase to something like http://mywebserver:8080. The property can be set either within the server code, or else with the -D option on the command line when the server is started. Obviously, there must also be a web server running to serve up the class stubs. Sun includes a simple class server with the RMI distribution. On the client side, the stub can be downloaded at runtime by providing the appropriate URL to the Naming.lookup() method.
2. Arguments and Return Values: In an RMI application, there can be three types of parameters that can be exchanged between the client and server:
3. Dynamic Class Loading and Behavioral Transfer: One of the major differences between RMI and CORBA (at least, standard CORBA 2.1) is that RMI supports behavioral transfer. Without going into the details, this basically means that by doing tricks with java interfaces, the remote server can perform operations that are determined at runtime. This is similar to the "Command" design pattern.
4. Firewalls: As of Java SDK 1.2 and higher, all RMI classes
are firewall ready, at least for SOCKS and HTTP proxy firewalls.
RMI applications can be configured for firewalls by setting various system
properties, which can be done at run time with the -D option.
Probably, in practice this will take a lot of experimentation to get to work for HTTP proxy firewalls. And of course this does not
address any of the problems that Kerberos has with firewalls.
5. SSL+RMI: Java 2 provides a standard extension for doing SSL
encryption, and SSL versions of the Java socket class are available.
Java sockets and server sockets make use of the Factory pattern, and by extending the RMIServerSocketFactory and RMIClientSocketFactory classes, we can transform our implementation file so that it is delivered over an SSL channel. Interestingly,
we would not need to change our client and server code, just the implementation file. The reason is that when we call the UnicastRemoteObject's exportObject() method in the implementation file, we specify here that we wish to use SSL sockets.
6. Activatable Objects: As of SDK 1.2, RMI objects can exported
as activatable. This means that if a client connects to a server
and downloads the stub, and the server crashes or otherwise becomes unavailable
temporarily, then the state of the remote object is restored when the server
comes back up, and the client need not be restarted. Activatable
objects need to use the RMID support service provided by Sun.
This daemon can restore all JVMs and server objects on the machine running
A. Jini Core Concepts
Usually, Jini is presented as a technology for coupling together a wide assortment of computers and intelligent devices such as networked printers and digital cameras and so on. We don't have to wait for these technologies to find their markets, however, to take advantage of Jini. Objects are free, so we will take the point of view here that Jini really is designed for networking systems of distributed components.
Jini is complimentary to, not a replacement for, RMI or CORBA, filling in some of the capabilities glossed over in more traditional client-server systems. These capabilities include the following:
C. Discovery and Join
For a class to make use of Jini services, it must first find at least one running lookup service. This process is called discovery. Really, it is just a lookup of lookup services.
Jini defines two fundamental units of organization for a collection of objects (clients and services). These are groups and federations. Groups can be defined by any convenient boundary, such as a company department. Federations are somewhat more interesting. These are spontaneous communities that can form between clients and services and are especially relevant to intelligent agent systems. Presumably federations can themselves form larger federations and so on. The ideas of groups and federations acknowledge the fact that a client will typically not want to look up a particular service from among every possible service provider in the world, but really from its neighborhood first, and then to nearby neighborhoods, and so on.
Jini clients and services are designed to be loosely coupled.
They must publish their existence to a look up service. The
complimentary action is known as discovery, in which the interested
client contacts the look up service to find which services it has published.
When the client has successfully contacted a lookup service, it gains a
reference called a registrar. This is a local Java object
that must be instantiated in the client code, and is a member of the ServiceRegistrar
class. Sun distributes a lookup service implementation with Jini
called reggie, but presumably you could write your own. Reggie uses RMI to deliver proxy classes, so it is RMI-dependent even if the discovery process is RMI-independent.
You have basically two discovery protocols to use in your Jini applications. These have nice java classes/interfaces that hide a lot of the low level details. The two discovery methods are
D. Lookup Services
Once an object has discovered and joined a particular jini community (centered around one or more lookup services) it then uses those lookup services to interact with the other members. But what exactly is it that you get when you lookup a service? Jini services are delivered to clients as downloadable proxies. This is mobile code. The proxy is used to create an entirely new object within the client's VM. This object may be local, an RMI stub, or a stub from CORBA. Interesting, Jini lookup services store entire (serialized) objects of any sort. By comparison, the RMI Registry itself only stores RMI stubs. The client uses the lookup service as a library to check out services. The client may check out the service entire, or it may get a stub that allows the service's methods to be invoked remotely. Jini does not care.
Once a client has checked out a service from the lookup service, there is a relationship established between the client and the service, and all of the problems of networked applications begin. To lessen the impact of this, Jini introduces the concept of service leases, which means that a client only checks out the service for a specified amount of time. After the time expires, the service discontinues trying to communicate with the client. So if that client had died during the application, the service will eventually free up the resources associated with that client interaction.
Many leasing options are available. Leasers can deny services, clients can renew leases, or they can be canceled early if no longer needed. On a bit more complicated note, these leases can be handled through third parties. That is, an intermediary can check out a service on my behalf, as me.
F. Remote Events
As one might imagine, the process of sending events around a distributed system has many problems:
Unlike the standard Java event model, which defines a plethora of event types, all Jini events implement the same interface. There is only one type of event: remote. Jini events can be delegated to third parties. The idea is that you write third party event handlers to take care of the quality of delivery needs of each client. This is custom code, rather than a standard set of interfaces. So I could have one delegate called Mailbox that freeze dries all of the events generated while an interested listener is detached from the system. I could have another delegate (Telemarketer) that keeps trying and trying to reach the registered listener until a connection is made or the appropriate lease expires.
Transactions group related operations into a composite operation with a boolean condition (success or failure) that depends on the outcomes of the component pieces. For example, a transaction that debits one account and credits another should be grouped into a single composite operation. If the debit operation is successful, but the credit operation fails, the entire operation should be voided and no data should be changed.
The Sun distribution comes with an implementation of a transaction manager that is called mahalo.
Going beyond the core specification, CORBA provides supplementary specifications for so-called CORBAservices, which vendors may optionally provide. One of the most commonly available is the naming service, which eliminates the need to look up the IOR and allows the client to look up objects by handles. Typically, CORBA vendors provide core CORBA and common services like Naming for free, but charge for other services like security.
Note with WebFlow we have (in effect) our own naming service. The client must find the master server by getting its IOR, but once this is done, the client can connect to any available slaves belonging to that master. In practice, this has some long term disadvantages, since we have introduced custom code perhaps unnecessarily, when we could get more reliable, thoroughly tested code from a vendor. And it also takes us down a divergent path from the rest of the world.
The activatable objects of RMI are not, to my knowledge, analogous to anything in CORBA. We certainly don't have anything like this capability in WebFlow.
CORBA is just now (2.3) to the point that it can pass objects and parameters by value (that is, create local copies). In earlier versions of CORBA, it is not possible to have more than fairly primitive return types and arguments (ints, floats, strings, structs,...) unless these are also IDL interfaces (that is, also remote objects). I presume this is required for compatibility with C and other older languages. RMI does not have this restriction.
CORBA allows for dynamic method invocation: objects can be discovered by servers at runtime, and their methods can be invoked. In CORBA parlance, this is called introspection. WebFlow uses this to (for example) dynamically add contexts and modules to the master server. The master will not know what slaves it will have added to it, what modules these slaves will use and so forth. This is quite sexy for C code, but Java already comes with this capability (reflection).
CORBA is a specification produced by a consortium of 800+ vendors, and, in my opinion, it often shows. CORBA must be all things to all people in all languages, whereas RMI is limited to a single language. For internal development consistency, RMI appears to be relatively straightforward to use for experienced Java programmers, and many of the more sophisticated features are much better hidden under the hood for application developers than in CORBA. It is worth noting that while the CORBA specification comes with the weight of the OMG behind it (including IBM, HP, Oracle, and even Sun), it appears to me that most of the actual implementations have been done by relatively small vendors.