Using Activation: Persistence
|
Documentation Contents |
This tutorial describes how to implement an activatable remote object that uses
persistent state in its implementation. This tutorial uses a Setup
program (described in the tutorial Using Activation: the
Setup
Program) that registers information about an activatable
remote object with the JavaTM Remote Method Invocation (Java RMI) activation system daemon (rmid
)
and then binds a stub for that remote object in an rmiregistry
so that clients can look it up. You may want to read that tutorial before this
one.
This tutorial has the following steps:
Setup
programThe files needed for this tutorial are:
Counter.java
- a remote interface for countingCounterImpl
- an "activatable" implementation of the remote interfaceCounterClient.java
- a client that uses the remote interfaceclient.policy
- the security policy
file for the client There are a few basic ways to implement an activatable remote object. This tutorial describes how to implement an activatable remote object that uses persistent state in its implementation.
A remote object is activated when a client invokes a remote method on a stub for an activatable remote object. A stub for an activatable remote object contains the remote object's activation ID and information on how to contact the Java RMI activation system daemon (
rmid
) for the remote object. If the stub cannot connect to the last-known address (i.e., host/port) for the remote object, the stub will contact the remote object's activator (rmid
) to activate the object. Whenrmid
receives an activation request, it starts the remote object's activation group (or container) virtual machine (VM) if the group is not already executing, and thenrmid
asks the group to make an instance of the remote object. Once the group constructs the remote object, it returns the remote object's stub tormid
which, in turn, returns the actual stub to the initiating stub so that the initiating stub can update its information on how to contact the remote object in the future.Before any of this activation can take place, an application must register information about the activatable remote objects it needs to use. The following separate tutorial describes the information needed to activatate a remote object and how to register this information with
rmid
:This example defines the remote interface
Counter
with a single operation,increment
. The implementation classCounterImpl
persistently stores the result of the operationincrement
in a file, so that the result of theincrement
operation will survive across an object activation/deactivation cycle or even survive a machine reboot or crash. To make all this work, aCounterImpl
object, when activated, needs the name of the file it will use to read and store counter values. A user can register an activation descriptor that contains this file name (in aMarshalledObject
) as the "initialization data". The object's activation group will pass this pre-registered marshalled data (the file name) to the object's constructor when the group constructs the object during activation.The remote interface
examples.activation.Counter
is defined as follows:package examples.activation; import java.rmi.Remote; import java.io.IOException; public interface Counter extends Remote { int increment() throws IOException; }The implementation class,
examples.activation.CounterImpl
, for the activatable remote object is as follows:package examples.activation; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.rmi.MarshalledObject; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationID; public class CounterImpl implements Counter { private RandomAccessFile raf; private int count; private final Object countLock = new Object(); public CounterImpl(ActivationID id, MarshalledObject data) throws Exception { if (data != null) { String filename = (String) data.get(); synchronized (countLock) { count = openFile(filename); } System.err.println("count upon activation = " + count); } Activatable.exportObject(this, id, 0); } private int openFile(String filename) throws IOException { if (filename != null && !filename.equals("")) { File file = new File(filename); boolean fileExists = file.exists(); raf = new RandomAccessFile(file, "rws"); return (fileExists) ? raf.readInt() : writeCount(0); } else { throw new IOException("invalid filename"); } } private int writeCount(int value) throws IOException { raf.setLength(0); raf.writeInt(value); return value; } public int increment() throws IOException { synchronized (countLock) { return writeCount(++count); } } }The class
CounterImpl
implements the remote interfaceCounter
, but does not extend any class.The class declares a special "activation" constructor that an activation group calls to construct an instance during the activation process. This special constructor takes two parameters:
- The first parameter,
ActivationID
, is an identifier for the activatable remote object. When an application registers an activation descriptor withrmid
,rmid
assigns it an activation ID, which refers to the information associated with the descriptor. This same activation ID (also contained in the remote object's stub) is passed to this constructor when the remote object is activated.- The second parameter is a
MarshalledObject
that contains initialization data pre-registered withrmid
. In this example, the marshalled data is the name of a file containing the object's persistent state which is the latest counter value.The constructor obtains the file name contained in the
MarshalledObject
which was passed as the second parameter. Next, the constructor calls the local methodopenFile
to open the file and return the current counter value. If the file exists, theopenFile
method reads the value last saved to the file; otherwise, it creates a new file and initializes the count to zero. The constructor then calls the static methodActivatable.exportObject
, passing the implementation itself (this
), the activation ID, and the port number0
, indicating that the object should be exported on an anonymous TCP port. While this implementation does not use the activation ID passed as a parameter to the constructor, another implementation may wish to save the activation ID for future use, in order to deactivate the object, for example.Finally, the class implements the remote interface's single method,
increment
to increment the count, save the count to the file, and return the incremented count. Note that there is a flaw in the implementation of thewriteCount
method. If the machine crashes between the calls tosetLength
andwriteInt
, the value of the counter will be lost. Making this implementation more robust to a crash within that small window is an exercise left to the reader.
The
CounterClient
program looks up a remote object's stub (one that implements the remote interfaceCounter
) in the registry on the host supplied as the optional first argument, and then invokes the stub'sincrement
method and displays the result. When this client invokes a remote method on the stub acquired from the registry, the remote object will activate if not already active.The source for the program is as follows:
package examples.activation; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class CounterClient { public static void main(String args[]) throws Exception { String hostname = "localhost"; if (args.length < 1) { System.err.println( "usage: java [options] examples.activation.CounterClient [host]"); System.exit(1); } else { hostname = args[0]; } if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } String name = System.getProperty("examples.activation.name"); Registry registry = LocateRegistry.getRegistry(hostname); Counter stub = (Counter) registry.lookup(name); System.err.println("Obtained stub from the registry."); System.err.println("Invoking increment method..."); int count = stub.increment(); System.err.println("Returned from increment remote call."); System.err.println("count = " + count); } }This program should be run as follows:
java -cp clientDir \ -Djava.security.policy=client.policy \ -Dexamples.activation.client.codebase=clientCodebase \ -Dexamples.activation.name=name \ examples.activation.CounterClient [host]where:
- clientDir is the root directory for the client program (its class path),
- client.policy is the security policy file for this program,
- clientCodebase is the location (URL) for this program's class (used in granting permissions to this program in the client.policy file)
- name is the name for the object's stub in the registry
- host is the server's host
Note:
rmid
must be running on its default port, andrmiregistry
must be running on its default port (both on the remote host) prior to running this program.The following is an example
client.policy
file that grants the appropriate permissions for the activation examples:grant codeBase "${examples.activation.client.codebase}" { // permissions to read system properties required by the client permission java.util.PropertyPermission "examples.activation.name","read"; // permission to connect to the registry, activation system, and remote host permission java.net.SocketPermission "*:1024-","connect"; };The codebase to which permissions are granted is a file URL specifying the location of the client's classes. This file URL is the value of the
examples.activation.client.codebase
system property, defined when the client program is run. The client needs two permissions:
java.util.PropertyPermission
- to read the system propertyexamples.activation.name
that specifies the name for the stub in the registryjava.net.SocketPermission
- to connect to the registry, activation system, and remote object's host
The source files for this example can be compiled as follows:
javac -d implDir Counter.java CounterImpl.java javac -d clientDir Counter.java CounterClient.javawhere implDir is the destination directory to put the implementation's class files in, and clientDir is the destination directory to put the client's class files in.
Setup
programOnce your implementation phase is complete, you need to register information about the activatable object so a client can use it. The
Setup
program, described by the tutorial Using Activation: theSetup
Program, registers an activation descriptor for an activatable object withrmid
, and then binds the remote object's stub in anrmiregistry
so that clients can look it up.To run the
Setup
program for this example, see the section Startrmid
,rmiregistry
, and theSetup
program in theSetup
program tutorial, which describes how to startrmid
,rmiregistry
, and theSetup
program itself.After you run
rmid
andrmiregistry
as instructed in theSetup
tutorial, you will need to run theSetup
program to register an activation descriptor for an activatable object that implements the classexamples.activation.CounterImpl
. The following command line runs theSetup
program, supplying an appropriate file URL for each codebase used:java -cp setupDir:implDir \ -Djava.security.policy=setup.policy \ -Djava.rmi.server.codebase=file:/implDir/ \ -Dexamples.activation.setup.codebase=file:/setupDir/ \ -Dexamples.activation.impl.codebase=file:/impDir/ \ -Dexamples.activation.name=examples.activation.Counter \ -Dexamples.activation.policy=group.policy \ -Dexamples.activation.file=file \ examples.activation.Setup examples.activation.CounterImplwhere:
- setupDir is the root directory for the
Setup
program's class- implDir is the root directory for the implementation's classes
- setup.policy is the security policy file for the
Setup
program- group.policy is the security policy file for the activation group
- file is the name of a file containing the object's persistent state which is registered as the
data
in the object's activation descriptorNote that each file URL above has the required trailing slash. Examples of group and setup policy files, suitable for this tutorial, are described in the
Setup
tutorial, and are listed below:The output from the
Setup
program should look like this:Activation group descriptor registered. Activation descriptor registered. Stub bound in registry.
Once you have successfully registered an activation descriptor for a
CounterImpl
implementation, you can run the client program, which, during its first execution, will cause the activatable object to activate.The following command line illustrates how to run the client program, specifying a file URL for the client codebase:
java -cp clientDir \ -Djava.security.policy=client.policy \ -Dexamples.activation.client.codebase=file:/clientDir/ \ -Dexamples.activation.name=examples.activation.Counter \ examples.activation.CounterClient [host]where:
- clientDir is the root directory for the client program (its class path).
- client.policy is the security policy file for this program.
- name is the name for the object's stub in the registry.
- host is the server's host.
Notes:
- The client codebase must have a trailing slash.
- The name must match the name supplied to the
Setup
program. In this example, we used the nameexamples.activation.Counter
.rmid
andrmiregistry
must be running on the server's host. If the server's host is not the local host, the host argument must specify the remote host that they are running on.The output from the client should look like this:
Obtained stub from the registry. Invoking increment method... Returned from increment remote call. count = 1
Copyright 2004
Sun Microsystems, Inc. All Rights Reserved.
Please send comments to: rmi-comments@java.sun.com |