Section 13.7. Remote Object Activation


13.7. Remote Object Activation

Automatic activation of remote objects was added to RMI as of Java 1.2. The activation subsystem in RMI provides you with two basic features: the ability to have remote objects instantiated (activated) on demand by client requests and the ability for remote object references to remain valid across server crashes, making the references persistent. These features can be quite useful in certain types of distributed applications.

For example, think back to the AccountManager class we discussed when we talked about factory objects. We might not want to keep the AccountManager running on our server 24 hours a day; perhaps it consumes lots of server resources (memory, database connections, etc.), so we don't want it running unless it is being used. Using the RMI activation service, we can set up the AccountManager so that it doesn't start running until the first client requests an Account. In addition, after some period of inactivity, we can have the AccountManager shut down to conserve server resources and then reactivate the next time a client asks for an Account.

If a remote object is made activatable, it can be registered with the RMI registry without actually being instantiated. Normally, RMI remote objects (based on the UnicastRemoteObject interface) provide only nonpersistent references to themselves. Such a reference can be created for a client only if the referenced object already exists in a remote Java VM. In addition, the remote reference is valid only during the lifetime of the remote object. The remote object activation service adds support for persistent remote references that can be created even if the remote object is not running at the time of the request. This can persist beyond the lifetime of an individual server object.

The key features provided by the RMI activation service include:

  • The ability to automatically create remote objects, triggered by requests for references to these objects.

  • Support for activation groups , in which groups of activatable remote objects are executed in the same Java VM, which is automatically started by the activation service if needed.

  • The ability to restart remote objects if they exit or are destroyed due to a system failure of some kind. This can add a certain degree of fault tolerance to RMI applications.

In the RMI activation system, activatable objects belong to activation groups, and each activation group runs within its own Java VM. If you don't group your activatable objects, instead simply assigning a new activation group to each activatable object you create, each object then runs inside a separate Java VM.

You typically define an activatable remote object by:

  • Subclassing your remote object implementation from the Activatable class provided in the java.rmi.activation package

  • Providing activation constructors in the server implementation

  • Registering the object and its activation method with the activation service

If you want remote clients to directly access your activatable object, you also need to register the object with the RMI registry, so that it can be found by name on the network. You can register an activatable class with the registry without actually creating an instance of the remote object, as we'll see shortly.

You can also create an activatable object without subclassing the Activatable class. This might be necessary if you need to extend another class and the Java single-inheritance limit keeps you from also extending Activatable. For most of this section, we'll just discuss the case in which you're subclassing Activatable; we'll mention this other approach only when needed.

13.7.1. Persistent Remote References

The primary difference between an activatable remote object and a nonactivatable one is that a remote reference to an activatable object doesn't need to have a "live" object behind it. If an activatable object is not running (e.g., it hasn't been constructed yet, it has been garbage-collected by its Java VM, or its VM has exited), a remote reference to the object can still be exported to a client. The client receives a stub, as usual, and can make remote method invocations through the stub. When the first method is invoked, the activation service running on the server sees that the object is not active and goes about activating the object for the client. If the object doesn't have a VM to run in, the activation system starts one. The object is then activated using information that has been registered with the activation system. This information includes the object's class name, a URL that can load the class bytecodes if they're not found in the local CLASSPATH, and data to pass into the object's activation constructor. Once the object has been activated, the method invocation takes place, and the results are marshaled and sent back to the client.

As long as the object stays running, future method requests are handled as usual. If the object stops running for some reason (e.g., it is garbage-collected, or its VM dies), the next method request by a client triggers the activation service again, and the object is reactivated. This is what is meant by persistent remote references : remote references to activatable objects can persist across multiple lifetimes of the actual server object.

13.7.2. Defining an Activatable Remote Object

Naturally, before you can register and use an activatable object with the RMI activation system, you need to define the remote interface and the server implementation for the object. The java.rmi.activation package provides the classes you need to define an activatable remote object. You usually define a remote object as activatable by subclassing it from Activatable and defining a special constructor that activates the object. You also have to register the object with the activation service on the server host.

Other than that, the implementation of an activatable remote object is similar to that of a nonactivatable one. Start with a remote interface that contains the methods you want to export from your object. The interface should extend Remote, and each method should throw a RemoteException (or, as of Java 1.2, any parent of RemoteException). The server implementation implements this interface and extends a concrete implementation of the java.rmi.server.RemoteServer class. Since you're defining an activatable remote object, you typically extend java.rmi.activation.Activatable directly and use its constructors to initialize, register, and activate your remote object. If you choose not to extend Activatable directly, you have to use the static exportObject( ) methods on the Activatable class to register your object with the activation runtime system.

13.7.2.1. The Activatable class

The Activatable class has four constructors. Here are signatures for two of them:

     protected Activatable(String src, MarshalledObject data,       boolean restart, int port) throws RemoteException     protected Activatable(String src, MarshalledObject data,       boolean restart, int port, RMIClientSocketFactory csfactory,       RMIServerSocketFactory ssfactory) throws RemoteException 

These two constructors are initialization constructors. Use them when you decide to proactively create one of your remote objects and register it with the RMI activation service. In this case, the object already exists when a client first makes a method request on it, but if the object is destroyed, the next client request causes the object to be reactivated. These constructors register an object with the local activation service and export the object so that it can receive remote method requests. Both constructors have the following arguments in common:

  • The String parameter is a URL that indicates where class bytecodes required by this object can be located. This information is exported to a remote client so it can dynamically load classes required to unmarshal method return values, for example. If a null value is passed in for this argument, remote clients of the activatable object can't dynamically load classes and therefore need the required classes available locally.

  • The MarshalledObject parameter provides initialization data for the object; this parameter is necessary because data is typically sent from the activation daemon's VM to the VM designated to run the activatable object and the two might not be the same (more on this later).

  • The boolean flag indicates whether the object should be automatically re-created when its home VM or its activation group is restarted (e.g., after a server restart).

  • The int parameter specifies the port on which the object is exported. A port of zero tells the RMI runtime system to export the object on a random open port.

The second initialization constructor takes custom client and server socket factories that create socket communications between the server and the clients of the object. Customized socket factories were added to RMI in Java 1.2.

The other two Activatable constructors have the following signatures:

     protected Activatable(ActivationID id, int port) throws RemoteException     protected Activatable(ActivationID id, int port,       RMIClientSocketFactory csfactory, RMIServerSocketFactory ssfactory)       throws RemoteException 

These constructors are (re)activation constructors. The activation system uses them to activate a remote object that has received a remote method request, but isn't currently active. The ActivationID is a persistent ID issued by the activation system for the remote object, and the port number is the port that exports the remote object. The second constructor again takes custom server and client socket factories.

The Activatable class also has a set of exportObject( ) methods that correspond to the constructors just described. You can use these methods when an activatable object doesn't directly extend the Activatable class. You call the appropriate exportObject( ) methods from within the constructors of the class, so they serve the same function as calling the Activatable constructors during initialization of an Activatable subclass.

13.7.2.2. Implementing an activatable object

You can implement an activatable remote object in two ways: derive the remote object from the Activatable class directly and make the required calls to the Activatable constructors in its constructors or have the class implement a Remote interface and make the required calls to the static exportObject( ) methods in its constructors.

In either case, when the activation system activates a remote object, it looks for a constructor on the class that takes two arguments: an ActivationID and a MarshalledObject. The activation system calls this constructor, passing in an ActivationID it generates for the object and the MarshalledObject registered for the activatable object by the first constructor we just discussed.

This means you have to provide a constructor with this signature in your implementation of an activatable object. In this constructor, you should call either one of the (re)activation constructors on the Activatable parent class (if your class extends Activatable) or the corresponding Activatable.exportObject( ) method (if you didn't extend Activatable). In this call, you pass on the ActivationID issued by the activation system and specify the port for the exported remote object (a port number of 0 causes the object to be exported on a random open port).

In addition to this required constructor, you can define other constructors for your remote object implementation as needed. If you want your object to be reactivatable, any additional constructors should call one of the initialization constructors on Activatable (using super( )) or the corresponding exportObject( ) method, passing in a valid source URL and a MarshalledObject to be used as an argument if the object is reactivated. If the object is destroyed at some point and a subsequent remote method request is received for it, the activation system reactivates the object by calling the required (re)activation constructor on the object's class, passing in this MarshalledObject argument.

Example 13-3 shows a partial listing of an activatable implementation of the Account interface from Example 13-1. The primary differences between this implementation and the nonactivatable one in Example 13-2 are that this new implementation extends the java.rmi.activation.Activatable class instead of UnicastRemoteObject, and its constructors support the activation system. We've also defined a helper class, AccountState, that is used to wrap the account's state information (e.g., its name and balance) so that it can be held within a MarshalledObject for initialization data. (This is described later in this section.)

Example 13-3. An activatable version of the account
 import java.rmi.server.UnicastRemoteObject; import java.rmi.activation.*; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.util.List; import java.util.ListIterator; import java.io.IOException; public class ActivatableAccountImpl extends Activatable implements Account {   // Our current balance   private float mBalance = 0;   // Name on account   private String mName = "";   // "Regular" constructor used to create a "preactivated" server   public ActivatableAccountImpl(String name)     throws RemoteException, ActivationException, IOException {     // Register and export object (on random open port)     super(null, new MarshalledObject(new AccountState(name, 0f)), false, 0);     mName = name;   }   // Constructor called by the activation runtime to (re)activate   // and export the server   protected ActivatableAccountImpl(ActivationID id, MarshalledObject arg)       throws RemoteException, ActivationException {     // Export this object with the given activation ID, on random port     super(id, 0);     System.out.println("Activating an account");     // Check incoming data (account state) passed in with activation request     try {       Object oarg = arg.get( );       if (oarg instanceof AccountState) {         AccountState s = (AccountState)oarg;         // Set our name and balance based on incoming state         mName = s.name;         mBalance = s.balance;       }       else {         System.out.println("Unknown argument type received on activation: " +                            oarg.getClass( ).getName( ));       }     }     catch(Exception e) {       System.out.println("Error retrieving argument to activation");     }   }   // Remainder of implementation is identical to AccountImpl   // .   // .   // . } // Define a structure to hold our state information in a single marshalled // object, so that we can register it with the activation system class AccountState {   public float balance = 0f;   public String name = null;   public AccountState(String n, float b) {     name = n;     balance = b;   } } 

The first constructor for ActivatableAccountImpl is a public one, used to construct an account for a user with the given name. The constructor registers the new object with the activation system by calling the constructor of its parent, Activatable. As we discussed earlier, the first argument to the Activatable constructor is the URL for the codebase (which is null, in this case, to indicate that we don't support dynamic classloading). The second argument, the (re)initialization data for the activatable object, is created by wrapping the state of the account (the name and balance, held in an AccountState object, a utility class defined at the end of the code listing) in a MarshalledObject. If the Account needs to be reactivated later, the activation daemon will use this MarshalledObject to initialize the reactivated Account, as explained later. The third and fourth arguments indicate that we want the object activated only on demand and that it can be exported on any random available port on the local machine.

The second constructor is used by the activation system and must be found on your implementation class when the activation system looks for it during a reactivation attempt. If an object of this type needs to be activated (or reactivated after a crash of some sort), this constructor is called to create the remote object. The constructor takes an ActivationID argument, issued by the activation system and the MarshalledObject that was previously registered for the object with the activation system. Our ActivatableAccountImpl constructor exports the object by calling the second constructor on the Activatable class, then initializes itself with the data from the MarshalledObject.

13.7.3. Registering Activatable Objects

There are several ways to register an activatable object with its local activation system. In each case, the activation system needs to be told how to create (or re-create) the object. The information the activation system needs to activate an object is encapsulated in the ActivationDesc class. An ActivationDesc object contains the name of the class for the remote object, a URL with the network location of the bytecodes for the class, a MarshalledObject to be used as the initialization data for the object, and the group assignment for the object.

The simplest way to register an Activatable object is to create an instance of the object. In our example, we've derived our server implementation from the Activatable class, so the public constructor on the ActivatableAccountImpl class registers the object by calling the necessary constructor on Activatable. Thus, we can create and register one of these as follows:

     // Make an activation group for the object     ActivationGroupDesc gdesc = new ActivationGroupDesc(null, null);     ActivationGroupID gid = ActivationGroup.getSystem( ).registerGroup(gdesc);     ActivationGroup.createGroup(gid, gdesc, 0);     // Make an Account object, which registers itself with activation system     Account account = new ActivatableAccountImpl(accountName);     // Register with naming service     LocateRegistry.getRegistry( ).rebind(accountName, account); 

The first four lines are required to create an activation group for our activatable object. We'll talk more about activation groups shortly. For now, all you need to know is that this code creates the default activation group for the current VM. Any remote object that isn't specifically assigned to a group is placed in this default group.

The activatable object itself is created by calling the public ActivatableAccountImpl constructor. This constructor registers the object with the activation system by calling the appropriate Activatable constructor, as we've already discussed. Since we haven't specified an activation group for the object, it is placed in the default group we just created. If we hadn't created that default group, the activation system would throw an exception here, when the object is registered.

Aside from the creation of the activation group, this example looks a lot like our other examples of registering RMI objects. The difference here is that if the process dies off at some point, the activation system can reactivate the activatable object in a new Java VM using the information provided in the ActivationDesc for the object. In this case, we're relying on the Activatable constructor (which is called by our ActivatableAccountImpl constructor) to create and register an ActivationDesc for our object using the information we passed into it when we called it from our constructor.

When an object needs to be activated, the activation system first looks up the ActivationDesc for the object and then looks for the class referenced in the ActivationDesc, using the codebase URL specified in the ActivationDesc to load the class bytecodes, if necessary. Once the class has been loaded, the activation system creates an instance of the class by calling the activation constructor, which takes an ActivationID and a MarshalledObject as arguments. The ActivationID is issued by the activation system, and the MarshalledObject contains the data previously registered with the ActivationDesc. In our activatable Account in Example 13-3, the activation system calls the second constructor on our ActivatableAccountImpl class. The new object passes the ActivationID up to the Activatable constructor so that it can be recorded, and the account state (name and balance) is pulled from the MarshalledObject. The Activatable constructor takes care of creating and registering an ActivationDesc for the object and exporting the object with the activation system.

13.7.3.1. Registering an activatable object without instantiating

A more complicated but often more useful way to register a remote object is to create an ActivationDesc for it and then register this information directly with the activation system, without creating an instance of the object. The static Activatable.register( ) method accepts an ActivationDesc object and registers it with the activation system directly. Here's how we can do that:

     // Make a codebase and activation argument for the object     String src = "http://objhost.org/classes";     MarshalledObject actArg = new MarshalledObject("MyAccount");     // Create the ActivationDesc and get a stub for the object     ActivationDesc desc =          new ActivationDesc("com.oreilly.jent.rmi.ActivatableAccountImpl",          src, actArg);     Account accountStub =          (Account)Activatable.register(desc); 

When we create the ActivationDesc for the object, we specify the name of the class to use for creating the object, a codebase for finding the class, and a MarshalledObject that is passed to the object when it's activated. The ActivationDesc is used in the call to the Activatable.register( ) method, which returns a RemoteStub for the activatable object. Since we know this stub is for an object that implements the Account interface, we can safely cast it to an Account. We can also use this reference to register the remote object with the local RMI naming registry:

     LocateRegistry.getRegistry( ).bind("Account", accountStub); 

Although it is not shown here, note that you also have to create an activation group for the object, just as we did in our earlier example, before you can register it with the activation service.

So, to recap, we've registered a remote object with the activation system and the RMI naming registry without actually creating the object itself. When a client tries to look up the object, it gets back a remote stub, with no active object behind it on the server. When the client calls a method on the stub, however, the activation system on the server creates the object, using the information in the ActivationDesc we provided.

13.7.3.2. Passing data with the MarshalledObject

The sole option for passing arguments to activatable objects before they are activated is through the MarshalledObject contained within the ActivationDesc for the object. However, once the ActivationDesc is registered with the activation system, you can't dynamically update the contents of the MarshalledObject. This is a serious problem in our example, if you've been following it closely. We wrap the account state (the account owner's name and the account balance) and register it with the activation system just once, when we create or register the object. As clients interact with the Account, the balance is going to change, but our registered MarshalledObject is not going to change along with it. If the VM of the Account crashes and the Account is reactivated using the MarshalledObject registered for it, the balance will be set back to the value it was when the object was first registered with the activation system.

There are a number of ways to get around this problem and make the initialization arguments more dynamic. One is to bundle a filename or URL into the MarshalledObject , rather than the data itself. We could then keep the data behind the file or URL updated with the current state of the Account. At the point that the object is activated, it can read data from the file or URL and use the up-to-date data during activation. Alternatively, if this were a real-world example and our account information were stored persistently in a database, we would just need to pass the account ID in the initialization parameters, and the Account object could then read the current state of the Account from the database.

13.7.4. Activation Groups

Every activatable RMI object belongs to an activation group. Each group of activatable objects runs within the same Java VM on the server host. In essence, activation groups are a way of defining collections of activatable remote objects that should share the same physical address space. We've already seen how to set up an activation group, since we had to do this before registering our activatable object with the activation system. In this section, we'll take a look at creating activation groups in a bit more detail and discuss what the activation group is actually doing for you.

Activation groups in RMI are more than just a way of organizing remote objects. Each activation group is responsible for monitoring, activating, and reactivating the objects it contains. Internally, remote requests from clients are intercepted by an ActivationMonitor that coordinates the active/inactive status of server objects with the ActivationGroup. If an object needs to be activated, an Activator is invoked to make the object active within the ActivationGroup. Note that you don't normally need to interact with the underlying components of the activation system. Simply set up your ActivationGroup objects and assign activatable objects to them; the activation system does the rest for you.

An ActivationGroup is created when the first object in the group needs to be activated. The Activator is responsible for creating a VM for the ActivationGroup to run in and for starting the ActivationGroup using the information in the registered object's ActivationGroupDesc, if it has one. If the remote object doesn't have a specified group, a default one is created. The new ActivationGroup object is then told to activate the requested remote object by calling its newInstance( ) method. The arguments the Activator passes into this method are the ActivationID for the new object and the ActivationDesc that the Activator has registered for the object.

The ActivationDesc gives an ActivationGroup everything it needs to activate the remote object. The ActivationGroup takes the class name for the object and looks for the class bytecodes. First it checks the local CLASSPATH, and if that pulls up nothing, it uses the URL in the ActivationDesc to load the class from the given URL. Once the class is loaded, an instance of the class is created by calling the activation constructor on the class (e.g., the constructor that has an ActivationID argument and a MarshalledObject argument). The ActivationID and MarshalledObject come from the call to the newInstance( ) method. The new, active remote object is returned to the Activator as a serialized MarshalledObject. This is done for two reasons. First, the Activator runs in a separate Java VM, so the active object reference needs to be transferred from one VM to another, and the easiest way to do this is to serialize it and transmit it in that form. Second, since the object has been bundled into a MarshalledObject, the Activator doesn't need to load the object's bytecodes unless absolutely necessary. In most cases, the Activator doesn't need to interact directly with the object itself, so it doesn't need to waste time loading unnecessary bytecodes.

Each ActivationGroup has an ActivationMonitor associated with it. The ActivationGroup has to tell the ActivationMonitor whenever an object becomes active or inactive. An activatable object is responsible for informing its ActivationGroup when it becomes active and inactive, by calling the group's activeObject( ) and inactiveObject( ) methods, respectively. The ActivationGroup, in turn, passes the information on to the ActivationMonitor by calling identical methods on the monitor object. When the object becomes inactive, the ActivationMonitor makes note of it and arranges for the object to be reactivated the next time a method request comes in for it. If an entire ActivationGroup becomes inactive, the ActivationMonitor is informed through its inactiveGroup( ) method. The next request for an object in that group causes the Activator to re-create the group.

13.7.4.1. Registering activation groups

An ActivationGroup is registered with the activation system in roughly the same way as an activatable object. You have to create an ActivationGroupDesc object that contains the name of the class for the group, the URL where the class bytecodes can be loaded, and a MarshalledObject that is given to the ActivationGroup as initialization data. Unlike activatable objects, though, the class of a group has to be a concrete subclass of ActivationGroup. Register the ActivationGroupDesc by calling the static ActivationSystem.registerGroup( ) method, passing in the ActivationGroupDesc. The ActivationSystem returns an ActivationGroupID that can assign specific objects to the group.

13.7.4.2. Assigning activatable objects to groups

Assign an activatable object to a group by specifying the group ID in the ActivationDesc registered with the activation system. The ActivationGroupID returned by the ActivationSystem.registerGroup( ) method can be passed into the ActivationDesc constructor.

Before you can register a remote object with the activation system, you need to create a group for it. For our activatable Account example, we can run Java code along the following lines on the object server (note that we've left out the exception handling):

     // Make an activation group for the object     ActivationGroupDesc gdesc = new ActivationGroupDesc(null, null);     ActivationGroupID gid =       ActivationGroup.getSystem( ).registerGroup(gdesc);     ActivationGroup.createGroup(gid, gdesc, 0);     // Set up ActivationDesc for object     String codebaseURL = "http://objhost.org/classes";     String accountName = "Fred";     MarshalledObject activationArg = new MarshalledObject(accountName);     ActivationDesc desc =       new ActivationDesc(gid, "com.oreilly.jent.rmi.ActivatableAccountImpl",                          codebaseURL, activationArg);     Account accountRef = (Account)Activatable.register(desc);     LocateRegistry.getRegistry( ).rebind(accountName, accountRef); 

Here we're using the ActivatableAccountImpl class and registering a remote object with the activation system without actually instantiating it. Before we register our remote object, we create an ActivationGroupDesc, then use it to register and create a new activation group with the activation system. After we create the activation group (using the ActivationGroup.createGroup( ) method), we use the ActivationGroupID for our new group to make an ActivationDesc for our remote object. We use that to register the object with the activation system. The activation system generates a remote stub for our object, and we register that with the RMI naming registry.

Since each ActivationGroup is started within its own VM if it's initially activated by the activation system, grouping objects is a convenient way to partition your remote objects into shared address spaces on your server.

13.7.5. The Activation Daemon

The heart of the RMI activation system is the activation daemon , which runs on the host for an activatable object. The activation daemon is responsible for intercepting remote method requests on activatable objects and orchestrating the activation of the object, if needed.

The activation daemon provided with the Java SDK, rmid, runs a Java VM that includes a java.rmi.activation.Activator object. The Activator is responsible for keeping a registry of activatable objects, along with the information needed to activate them. This information is in two parts: an ActivationDesc object and an optional ActivationGroupDesc. The ActivationGroupDesc identifies the group of activatable objects to which the object should be added and describes how to start the group if it doesn't exist. The ActivationDesc includes all information needed to activate the object itself. An activatable object has to be registered with the activation system in one of the ways described earlier to be started automatically by the Activator.

If a remote method request is received by the RMI runtime system on a host, and the target object hasn't been created yet, the Activator is asked to activate it. The Activator looks up the ActivationDesc (and ActivationGroupDesc, if present) for the object. If the object has an ActivationGroup assigned to it and the ActivationGroup doesn't exist yet, a Java VM is started for the group, and the ActivationGroupDesc data is used to start an ActivationGroup object within the new VM. If the object has no ActivationGroup associated with it, it's given its own ActivationGroup running in its own VM. The group is then asked to start the requested object, using the ActivationDesc object registered for the object. Once the ActivationGroup activates the object within its VM, the Activator is notified, and the now-active remote reference is returned to the RMI runtime system. The RMI runtime system forwards the remote method request through the reference to the object, and the return value is exported back to the client as usual.

13.7.5.1. The daemon's dual personality

When you start the rmid daemon, it creates an Activator and then listens on the default port of 1098 for activation requests. There is also a -port command-line option that lets you specify a different port for the VM to use. In addition to running the Activator, the rmid daemon also runs its own RMI Registry. If needed, you can register local objects with the daemon's internal Registry by specifying the daemon's port when you call the bind( ) or rebind( ) method of the Registry. For example, if rmid is running on its default port of 1098:

     RemoteObject server = ...     Registry local = LocateRegistry.getRegistry(1098);     local.bind(server, "Server"); 

This way, you can consolidate your activation system and your naming service into one VM on your server.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net