Activatable Jini services, Part 1: Implement RMI activation

The RMI activation framework can produce self-sufficient, flexible Jini services

A fundamental shift is taking place in the systems architectures of major software vendors. Presently, the industry aims to deliver services via software in such a way that users can interact with these services virtually anywhere, at any time, from any device. This new paradigm has been compared to a dial tone; practically anywhere in the world, one can pick up a phone and immediately hear a dial tone. Software services like banking, insurance, news, inventories, sales leads, and travel schedules may soon be as accessible as that familiar hum. IBM's WebSphere, HP's e-speak, and Microsoft's Microsoft .Net are all different manifestations of this ambitious desire, as is Sun's Jini.

TEXTBOX:

TEXTBOX_HEAD: Activatable Jini services: Read the whole series!

:END_TEXTBOX

The notion of a service lies at the heart of Jini. The Jini Specification defines a service as "... something that can be used by a person, a program, or another service ... Services will appear programmatically as objects in the Java programming language, perhaps made up of other objects ... A service will have an interface, which defines the operations that can be requested of that service. The type of the service determines the interfaces that make up that service." (See Resources for the Jini Specification.)

In Jini, a software component makes its services available through a proxy. Other services must locate that proxy to interact with a service within djinn, a federation of Jini services. Jini offers a robust mechanism, based on the Java language interfaces implemented by the proxy, that helps you look up proxies and other optional parameters. However, the ubiquitous computing environments promised by Jini and other service-oriented architectures require a service that is easily locatable and readily available.

For services with simple functionality, the proxy, once retrieved from the lookup service, might be able to operate independently -- making it a "strictly local" proxy. For instance, a service that converts decimal numbers into their hexadecimal equivalents would likely perform this computation entirely in the client's address space. For more involved services, the proxy might have to communicate with external resources running on other hosts. An example would be a currency converter, which must obtain up-to-date currency valuations to perform its service. For such a service, the proxy is not enough -- any other resources the proxy needs must also be on hand.

RMI in the Jini environment

The Jini Specification leaves the proxy, protocol, and server to communicate with the service implementation. The service implementation is hidden from the client, which only has to agree on the Java programming language interface, not a specific wire protocol, to interact with the service. Java Remote Method Invocation (RMI) can conveniently arrange such communication. All Jini services in Sun's implementation of the Jini Specification use RMI. Since Jini proxies can implement any part of the J2SE platform, of which RMI is a required component, any client VM using a Jini service proxy probably supports RMI.

RMI allows an object to make its method calls available to objects that reside in other virtual machines, and even on other hosts. An object that implements a subclass of java.rmi.Remote can export itself to the runtime to listen on a special port for incoming method invocations. A stub, typically generated by the rmic stub compiler that comes with the JDK, frees up methods specified in a Remote subtype and implemented from object to object. Once a stub is available to a client, that client can call methods on the stub. The stub then forwards method invocations to the remote object, and marshals and unmarshals method parameters as needed. In this scenario, a Jini service implemented as an RMI remote object will register its stub as the service object with lookup services. Figure 1 illustrates this process:

Figure 1: Client receives a service proxy from the lookup service, starts communication with the remote object

A loan approval service

To illustrate a Jini service that utilizes RMI, I have constructed a loan approval service for automobile buyers. This service collects information about the customer and the car he wishes to finance; it also indicates whether the bank will approve the loan, and if so, on what terms. If a bank implemented this service, it might present it as an applet on its Website, a module available via an ATM interface, a component accessed through a wireless PDA or cell phone, or simply as a voice interface on a telephone.

The LoanService interface has only one method:

      public interface LoanService 
      {
             public LoanApproval requestLoan(LoanRequest request) 
                  throws RemoteException;
      }

The LoanRequest object encapsulates information about the customer and the loan, such as the customer's annual income and credit report, the price of the car, and the requested term of the loan. The method returns a LoanApproval object, which provides details about the bank's terms for the loan, like the annual interest rate (APR) and the required amounts for the down payment and monthly installments. If the bank does not approve the application, the method returns a null value.

This service interface is not tied to RMI or any other specific implementation. It declares a java.rmi.RemoteException because we envision this service as operating in a distributed environment. Also, RemoteException is a useful class to account for exceptions that are due to network and communication failures. In order to deploy the service with RMI, we need to introduce a layer that extends java.rmi.Remote:

 
      public interface RemoteLoanService extends LoanService, 
            java.rmi.Remote 
      {
      }

A real-world implementation of this service would likely utilize an expert system to decide whether to write a loan. To keep this example simple, we will simulate this decision-making functionality with a Bank object. A property file stores the conditions that must be met for a loan to be approved. An instance of this object is initialized when the service starts up. Given a LoanRequest, the Bank object will determine if, and under what terms, the loan should be accepted. Figure 2 illustrates the service.

Figure 2: The loan approval service

RemoteLoanService is implemented as an RMI remote object, and therefore must accept method calls from other virtual machines. To do this, it exports itself to the RMI runtime, which in turn arranges for the object to listen on a special port for incoming method invocations. The java.rmi.server.RemoteServer class provides the semantics to create and export an object. In the RMI implementation introduced in JDK 1.1, once a remote object exports itself to the RMI runtime, it can accept remote method calls (i.e., stay active) "as long as the VM that created the object is running." (See Resources for the RMI Specification.)

UnicastRemoteObject, a subclass of RemoteServer, simplifies this approach to implementing remote objects. Follow these required steps to instantiate a service that is a subclass of UnicastRemoteObject:

  1. Create an instance of the object by calling one of its constructors
  2. Use the reference to this object and develop a service item
  3. Register the service item with Jini lookup services

For instance, our service implementation class would look like this:

public class LoanServiceImpl extends UnicastRemoteObject 
            implements RemoteLoanService 
{
        //Object that makes the decision about the loan
        Bank decisionSystem;
        //simple constructor to initialize the directoryPath
        public LoanServiceImpl(String path)
                  throws RemoteException
        {
                  super();                 
                  //initialize bank object from the property file
                   decisionSystem = initBank(path);
        }
        ....
              
        //implementation of LoanService
        public LoanApproval requestLoan(LoanRequest request)
                  throws RemoteException 
        {
                   //perform work here
                   return decisionSystem.decide(request);
        }
        //load Bank object 
        private Bank initBank(String filePath)
        {
                   //read property file from disk, initialize Bank object
                   ....
        }
        ...

The main() method of this class will create an instance of the object and register it with the Jini lookup service:

   public static void main(String[] argv) throws Exception 
   {
         //install an RMI security manager
         if (System.getSecurityManager() == null)
                    System.setSecurityManager(new RMISecurityManager());
         //collect directory path for bank property file on command line
         String path = argv[0];
                   
         //Create object. Note that we will need the remote interface,
         //represented by the stub.
         RemoteLoanService service = 
                  (RemoteLoanService) new LoanServiceImpl(path);
      //Join Jini lookup service
         JoinManager joinManager = new JoinManager(
                  service, 
                  null, 
                  serviceIDListener, 
                  null, 
                  null);
      }
} 

A skilled developer would declare every possible exception; however, to simplify the code presented here, we will just throw Exception.

The JoinManager class is a Jini helper utility. I will not discuss the semantics of using this class, but please note these important items:

  • The service might require some initialization data at startup. One example is the directory path to the bank's property file, which the command line specifies. The service ID is also initialization data; the first time a service joins a lookup, it receives a service ID. A ServiceIDListener implementation, which should save that ID to stable storage, notifies the object. Subsequent service registrations must reuse the ID; for instance, by implementing JoinManager's constructor, which allows the specifying of a ServiceID.
  • The service must accommodate some continuous activities. This includes constantly discovering and joining lookup services, renewing its leases in those services, and listening to events. An instance of JoinManager will take care of the first two duties. Since we passed a null instance in place of LookupDiscoveryManagement, JoinManager will use an instance of LookupDiscoveryManager to listen to discovery events from lookup services that are members of the public group. Similarly, since we specified null instead of LeaseRenewalManager, it will create and use one such object to manage leases.

The LoanServiceImpl object can be passed to a Java Virtual Machine (Java VM) for execution. We can run our loan service with a command line that specifies and passes properties to a Java VM, specifies the LoanServiceImpl class, and passes a parameter to the Java program itself. The properties include the Java security policy file required to run the LoanServiceImpl class, as well as the RMI codebase where clients that use the service's stub will download classes:

      /usr/java1.3/bin/java             -Djava.security.policy=/export/services/loanservice/loanservice.policy             -Djava.rmi.server.codebase=http://objectserver:8080/             LoanServiceImpl             /export/services/loanservice/bank.property

At this point, clients can locate the loan service proxy from lookup services. When a client calls the requestLoan() method, the stub will forward the method invocation to our LoanServiceImpl, which in turn will stand ready to process loan applications.

As long as the object is in an exported state, i.e., it can accept remote method calls, the VM will run continuously for objects that extend UnicastRemoteObject. Since LoanServicerImpl is a subclass of UnicastRemoteObject, the VM will not return after running the class's main method, but will instead continue running, allowing the object to accept remote calls.

RMI's architects foresaw the limitation of this approach: "Distributed object systems are designed to support long-lived persistent objects. Given that these systems will be made up of many thousands (perhaps millions) of such objects, it would be unreasonable for object implementations to become active and remain active, taking up valuable system resources for indefinite periods of time. In addition, clients need the ability to store persistent references to objects so that communication among objects can be re-established after a system crash, since typically a reference to a distributed object is valid only while the object is active."

That passage is from "Simple Activation for Distributed Objects" (see Resources), a paper written by Jim Waldo, Ann Wollrath, and Geoff Wyant. The paper also provides a solution to this problem and describes an implementation in the Modula 3 Network Object system. This work later found its way into Java and evolved into the Java activation architecture.

1 2 3 Page 1
Page 1 of 3