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:
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.
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 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:
java.rmi.Remote
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.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, optionally, 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 policy file is discussed
briefly, later in this document.
// 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
//
String location = "file:/home/rmi_tutorial/activation/";
// Create the rest of the parameters that will be passed to
// the ActivationDesc constructor
//
Properties props = new Properties();
// Because of the 1.2 security model, file writing has to
be
// explicitly allowed, as does socket creation. The first
// argument to the put method, inherited from Hashtable,
// is the key and the second is the value
//
props.put("java.security.policy",
"/home/rmi_tutorial/activation/policy");
ActivationGroupDesc.CommandEnvironment ace = null;
ActivationGroupID agi =
ActivationGroup.getSystem().registerGroup(
new ActivationGroupDesc(props, ace));
// 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 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
policy
file to be the location of the directory
on your system, where you've installed the example source code.
% rmiregistry -J-Djava.security.policy=/home/rmi_tutorial/activation/policy
&
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.polic
y file, please refer to to the following
documents:
http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.html
http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html
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 &
java
" commandjava
", 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).
% java -Djava.security.policy=/home/rmi_tutorial/activation/policy
-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://aHost/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
. 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
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.
|