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 Java 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.java
- an
"activatable" implementation of the remote interfaceCounterClient.java
- a client
that uses the remote interfaceclient.policy
-
the security policy file for the clientThere 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. When rmid
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 then rmid
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 to
rmid
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
class CounterImpl
persistently stores the result of
the operation increment
in a file, so that the result
of the increment
operation will survive across an
object activation/deactivation cycle or even survive a machine
reboot or crash. To make all this work, a CounterImpl
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 a
MarshalledObject
) 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
interface Counter
, 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:
ActivationID
, is an
identifier for the activatable remote object. When an application
registers an activation descriptor with rmid
,
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.MarshalledObject
that
contains initialization data pre-registered with rmid
.
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 method
openFile
to open the file and return the current
counter value. If the file exists, the openFile
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 method Activatable.exportObject
, passing
the implementation itself (this
), the activation ID,
and the port number 0
, 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 the
writeCount
method. If the machine crashes between the
calls to setLength
and writeInt
, 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 interface
Counter
) in the registry on the host supplied as the
optional first argument, and then invokes the stub's
increment
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:
Note: rmid
must be running on its default port, and
rmiregistry
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
property examples.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 hostThe source files for this example can be compiled as follows:
javac -d implDir Counter.java CounterImpl.java javac -d clientDir Counter.java CounterClient.java
where 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: the Setup
Program,
registers an activation descriptor for an activatable object with
rmid
, and then binds the remote object's stub in an
rmiregistry
so that clients can look it up.
To run the Setup
program for this example, see the
section Start rmid
,
rmiregistry
, and the Setup
program in
the Setup
program tutorial, which describes how to
start rmid
, rmiregistry
, and the
Setup
program itself.
After you run rmid
and rmiregistry
as
instructed in the Setup
tutorial, you will need to run
the Setup
program to register an activation descriptor
for an activatable object that implements the class
examples.activation.CounterImpl
. The following command
line runs the Setup
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.CounterImpl
where:
Setup
program's classSetup
programdata
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:
Notes:
Setup
program. In this example, we used the name
examples.activation.Counter
.rmid
and rmiregistry
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