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:
persistentObjectStore.ser)
MarshalledObject
, that it passes to the ActivationDesc
constructor, to store a java.io.File
object that represents the
location of the persistent data storagepersistentObjectStore.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
.
This tutorial is organized as follows:
Client4.java
,
the class which will invoke a method on an activatable objectYetAnotherRemoteInterface.java
,
the interface that extends java.rmi.Remote
, implemented by:MyPersistentClass.java
,
the class which is activatableSetup4.java
,
the class which registers information about the activatableFor the source code used in all of the activation tutorials, click here.
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:
import java.rmi.*;
import java.util.Vector;
public interface YetAnotherRemoteInterface extends Remote {
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
:
java.rmi.activation.Activatable
MarshalledObject
,
to save and restore the object's data stateimport java.io.*;
import java.rmi.*;
import java.rmi.activation.*;
import java.util.Vector;
Step 2:
Extend your class from java.rmi.activation.Activatable
public class MyPersistentClass extends Activatable
implements examples.activation.YetAnotherRemoteInterface {
In the constructor, in addition to the normal call to the super-class'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;
}
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 six steps to create a setup class:
SecurityManager
ActivationDesc
instancermid
rmiregistry
import java.io.File;
import java.rmi.*;
import java.rmi.activation.*;
import java.security.CodeSource;
import java.util.Properties;
System.setSecurityManager(new RMISecurityManager());
Step 3:
Create an ActivationDesc
instance
In the setup application, create a Properties
instance, an
ActivationGroupID
instance, a CodeSource
instance and
a MarshalledObject
instance, as these will be needed for the constructor
of the ActivationDesc
. 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, the path to (and in) the policy
file and the path to the persistentObjectStore.ser
to be the location of the directory on your system, where you've installed
the example source code.
// The "location" URL specifies where the class definition
// will come from when this object is requested (activated).
// Don't forget the trailing slash at the end of the URL
// or your classes won't be found.
//
java.net.URL location = new
java.net.URL("file:/home/rmi-tutorial/activation/");
// The second parameter to the CodeSource constructor
// is an object that implements java.security.PublicKey
//
CodeSource source = new CodeSource(location, null);
Properties props = (Properties)System.getProperties().clone();
// Because of the 1.2 security model, file writing has to be
// explicitly allowed. The first argument to the put method,
// inherited from Hashtable, is the key and the second is the
// value
props.put("java.policy", "/home/rmi_tutorial/activation/policy");
ActivationGroupID agi = ActivationGroup.getSystem().registerGroup(
new ActivationGroupDesc(props));
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
referenced by the CodeSource
//
ActivationDesc desc = new ActivationDesc
(agi, "examples.activation.MyPersistentClass",
source, data);
Step 4:
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 5:
Bind the stub, that was returned by the Activatable.register
method, to a name in the rmiregistry
Naming.rebind("MyPersistentClass", yari);
System.out.println("Exported MyPersistentClass");
Step 6:
Quit the setup application
System.exit(0);
There are six steps to compile and run the code:
rmic
on the implementation classrmiregistry
rmid
Step 1:
Compile the remote interface, implementation, client
and setup classes
% javac -d . YetAnotherRemoteInterface.java
% javac -d . MyPersistentClass.java
% javac -d . Client4.java
% javac -d . Setup4.java
Step 2:
Run rmic
on the implementation class
% rmic -d . examples.activation.MyPersistentClass
java
" command, followed by the property
name=value pair (no spaces from the "-D" all the way thought
the last "/") and then the fully-qualified package name of the setup program.
There should be a space just after the word "java"
and just before
the word "examples
" (which is very hard to see when you view this
as text, in a browser, or on paper).
% java -Djava.rmi.server.codebase=file:/home/rmi-tutorial/activation/
examples.activation.Setup4
The codebase property will be resolved to a URL, so it must have the
form of "http://somesource/"
or "file:/myDirectory/location/
"
or, due to the requirements of some operating systems, "file:///myDirectory/location/
"
(three slashes after the "file:
").
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.
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.
The server output should look like this:
Got the stub for MyPersistentClass
Exported MyPersistentClass
The argument to the client program is the hostname of the implementation
server, in this case, "vector
".
% java examples.activation.Client4 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
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
persistentObjectStore.ser
file increase, with each subsequent client call.Copyright © 1998 Sun Microsystems, Inc. All Rights Reserved.