Using a MarshalledObject to create persistent data
This tutorial should not be the first activation tutorial that you read.
This tutorial assumes that you have already read one or more of the
three introductory activation tutorials:
In the case of a UnicastRemoteObject, it is easy to pass command-line arguments to the implementation class, because the server
program that received those arguments is always running during the
lifetime of the remote object implementation. For activatable objects,
however, the setup class may exit immediately after registering the
activation descriptor with the RMI daemon and registering the stub with
the rmiregistry.
The MarshalledObject class provides a flexible
mechanism for passing persistence or initialization data through the
ActivationDesc, registered with rmid, rather
than hard-coding values into the implementation's class file.
Note: For the remainder of this tutorial, the terms
"activatable object implementation", "activatable object," and
"implementation" may be used interchangeably to refer to the class,
examples.activation.MyPersistentClass, which
implements a remote interface and is activatable.
In this tutorial the setup class,
examples.activation.Setup4, does two new things:
It constructs a java.util.Properties object to
pass the location of the java.security.properties
file to the constructor of an
ActivationGroupDescriptor, which in turn, gets
passed to the constructor of the ActivationDesc.
It uses the MarshalledObject, that it passes
to the ActivationDesc constructor, to store a
java.io.File object that represents the location
of the persistent data storage.
In this example, if the persistentObjectStore.ser file
exists, the activatable object implementation is initialized with the
persistent data from the file. Otherwise, if the file does not exist,
the activatable object initializes itself as though this is the first
time a client has tried to send data.
The client program, examples.activation.Client4, passes
a vector of transaction-like data to the activatable object, and that
data is added to the implementation object's vector. Each time a client
calls the implementation (to add more transaction data), the
activatable implementation stores its state (writes the vector) out to
the file specified by the MarshalledObject.
Setup4.java,
the class which registers information about the activatable
class with the RMI registry and the RMI daemon
You may notice that while the client code is included, it is not
discussed in a step-by-step manner, like the implementation and setup
classes. The reason for this omission, is that the client code for
activatable objects is no different than the RMI client code for
accessing non-activatable remote objects. Activation is strictly a
server-side implementation decision.
For all of the source code used in the activation tutorials, you
may choose from these formats:
Create an interface that describes each of the methods that you
would like to call remotely. For this example, the remote interface
will be
examples.activation.YetAnotherRemoteInterface. There are
three steps to create a remote interface:
Make the appropriate imports in the interface
Extend java.rmi.Remote
Declare each of the methods that may be called remotely
Step 1: Make the appropriate imports in your interface
import java.rmi.*;
import java.util.Vector;
Step 2: Extend java.rmi.Remote
public interface YetAnotherRemoteInterface extends Remote {
Step 3: Declare each of the methods that may be called remotely
public Vector calltheServer(Vector v) throws RemoteException;
Creating the implementation class
For this example, the implementation class will be
examples.activation.MyPersistentClass. There are five steps
to create an activatable implementation class that uses a
MarshalledObject:
Step 2: Extend your class from java.rmi.activation.Activatable
public class MyPersistentClass extends Activatable
implements examples.activation.YetAnotherRemoteInterface {
Step 3: Declare a two-argument constructor in the implementation
class
In the constructor, in addition to the normal call to the
superclass's constructor, in this example the MarshalledObject
is used to specify the file name of the persistent data store.
If the file exists, it's used to initialize this object's variable, a
Vector named "transactions". If the file
object doesn't exist, then the vector is manually initialized. If
there is any error reading the file, then object construction fails.
private Vector transactions;
private File holder;
public MyPersistentClass(ActivationID id, MarshalledObject data)
throws RemoteException, ClassNotFoundException, java.io.IOException {
// Register the object with the activation system
// then export it on an anonymous port
super(id, 0);
// Extract the File object from the MarshalledObject that was
// passed to the constructor
//
holder = (File)data.get();
if (holder.exists()) {
// Use the MarshalledObject to restore my state
//
this.restoreState();
} else {
transactions = new Vector(1,1);
transactions.addElement("Initializing transaction vector");
}
}
Step 4: Write the methods that use the
MarshalledObject, to save and restore the object's data
state
// If the MarshalledObject that was passed to the constructor was
// a file, then use it to recover the vector of transaction data
//
private void restoreState() throws IOException, ClassNotFoundException {
File f = holder;
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
transactions = (Vector)ois.readObject();
ois.close();
}
private void saveState() {
try {
File f = holder;
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(getTransactions());
oos.close();
} catch (Exception e) {
throw new RuntimeException("Error saving vector of data");
}
}
Step 5: Implement the remote interface method(s)
Add each of the vector elements passed from the client to the object
instance and save the updated vector out to a file.
public Vector calltheServer(Vector v) throws RemoteException {
int limit = v.size();
for (int i = 0; i < limit; i++) {
transactions.addElement(v.elementAt(i));
}
// Save this object's data out to file
//
this.saveState();
return transactions;
}
Creating the "setup" class
The job of the "setup" class is to create all the information
necessary for the activatable class, without necessarily creating an
instance of the remote object. For this example, the setup class will
be examples.activation.Setup4.
The setup class passes information about the activatable class to
rmid, registers a remote reference (an instance of the
activatable class's stub class) and an identifier (name) with the
rmiregistry, and then the setup class may exit. There are
seven steps to create a setup class:
Note: In this example, for simplicity, we will use a policy file that gives global permission to
anyone from anywhere. Do not use this policy file in a production
environment. For more information on how to properly open up
permissions using a java.security.policy file, please
refer to to the following documents:
In the setup application, the job of the activation group descriptor
is to provide all the information that rmid will require
to contact the appropriate existing JavaTM virtual machine* (JVM) or spawn a new JVM for the
activatable object.
Note: In order to run this code on your system, you'll
need to change the policy file location to be the absolute path to
where you've installed the example policy file that came with the
source code.
// Because of the Java 2 security model, a security policy should
// be specified for the ActivationGroup VM. The first argument
// to the Properties put method, inherited from Hashtable, is
// the key and the second is the value
//
Properties props = new Properties();
props.put("java.security.policy",
"/home/rmi_tutorial/activation/policy");
ActivationGroupDesc.CommandEnvironment ace = null;
ActivationGroupDesc exampleGroup = new ActivationGroupDesc(props, ace);
// Once the ActivationGroupDesc has been created, register it
// with the activation system to obtain its ID
//
ActivationGroupID agi =
ActivationGroup.getSystem().registerGroup(exampleGroup);
Step 4: Create an ActivationDesc instance
In the setup application, the job of the activation descriptor is to
provide all the information that rmid will require to
create a new instance of the implementation class.
Note: In order to run this code on your system, you'll
need to change the file URL location to be the location of the
directory on your system, where you've installed the example source
code.
// Don't forget the trailing slash at the end of the URL
// or your classes won't be found
//
String location = "file:/home/rmi_tutorial/activation/";
// Pass the file that we want to persist to as the Marshalled
// object
MarshalledObject data = new MarshalledObject (new File(
"/home/rmi_tutorial/activation/persistentObjectStore.ser"));
// The second argument to the ActivationDesc constructor will be used
// to uniquely identify this class; it's location is relative to the
// URL-formatted String, location.
//
ActivationDesc desc = new ActivationDesc
(agi, "examples.activation.MyPersistentClass", location, data);
Step 5: Declare an instance of your remote interface and register
the activation descriptor with rmid
YetAnotherRemoteInterface yari =
(YetAnotherRemoteInterface)Activatable.register(desc);
System.out.println("Got the stub for MyPersistentClass");
Step 6: Bind the stub, that was returned by the
Activatable.register method, to a name in the
rmiregistry
Note: Before you start the
rmiregistry, you must make sure that the shell or window in which you
will run the registry, either has no CLASSPATH set
or has a CLASSPATH that does not include the path to any classes that
you want downloaded to your client, including the stubs for your remote
object implementation classes.
If you start the rmiregistry, and it
can find your stub classes in its CLASSPATH, it will ignore the
server's java.rmi.server.codebase property, and as
a result, your client(s) will not be able to download the stub code for
your remote object.
Step 4: Start the activation daemon, rmid
% rmid -J-Djava.security.policy=rmid.policy &
Where rmid.policy is the name of the security policy file for rmid.
Run the setup, setting the codebase property to be the location of
the implementation stubs. There are four things that need to go on the
same command line:
The "java" command
A property name=value pair that specifies the
location of the security policy file
A property to specify where the stub code lives (no spaces
from the "-D" all the way though the last "/")
The fully-qualified package name of the setup program.
There should be one space just after the word "java", one
between the two properties, and a third one just before the word
"examples" (which is very hard to see when you view this
as text, in a browser, or on paper).
The codebase property will be resolved to a URL, so it must have the
form of "http://aHost/somesource/" or
"file:/myDirectory/location/" or, due to the requirements
of some operating systems, "file:///myDirectory/location/"
(three slashes after the "file:").
While a file: URL is sometimes easier to use for
running example code, using the file: URL will mean that
the only clients that will be able to access the server are those that
can access the same files system as the server (either by virtue of
running on the same machine as the server or by using a shared
filesystem, such as NFS). If you wish to use an HTTP server, but don't
have one available to you, please feel free to download
our HTTP server.
Please note that each of these sample URL strings has a trailing
"/". The trailing slash is a requirement for the URL set by the
java.rmi.server.codebase property, so the implementation
can resolve (find) your class definition(s) properly. For more
information on setting the java.rmi.server.codebase
property from the command line, please take a look at our tutorial on
dynamic code downloading using the
java.rmi.server.codebase property.
If you forget the trailing slash on the property, or if the class
files can't be located at the source (they aren't really being made
available for download) or if you misspell the property name, you'll
get thrown a java.lang.ClassNotFoundException. This
exception will be thrown when you try to bind your remote object to the
rmiregistry, or when the first client attempts to access
that object's stub. If the latter case occurs, you have another problem
as well because the rmiregistry
was finding the stubs in its CLASSPATH.
The server output should look like this:
Got the stub for MyPersistentClass
Exported MyPersistentClass
Step 6: Run the client
The argument to the client program is the hostname of the implementation
server, in this case, "vector".
The first time that the client is run against this implementation,
the output should look like this:
Got a remote reference to the class MyPersistentClass
Called the remote method
Result:
Initializing transaction vector
Deposited money
Withdrew money
Transferred money from Savings
Check cleared
Point-of-sale charge at grocery store
The second time that the client is run against this implementation,
the output will include five additional "transactions", so it should
look like this:
Got a remote reference to the class MyPersistentClass
Called the remote method
Result:
Initializing transaction vector
Deposited money
Withdrew money
Transferred money from Savings
Check cleared
Point-of-sale charge at grocery store
Deposited money
Withdrew money
Transferred money from Savings
Check cleared
Point-of-sale charge at grocery store
Additionally, you should see the size of the persistentObjectStore.ser
file increase, with each subsequent client call.
*As used on this web site, the terms "Java virtual machine" or "JVM"
mean a virtual machine for the Java platform.