The Remote Method Invocation (RMI) application programming interface (API) enables
client and server communications over the net between programs written in the JavaTM
programming language. The Enterprise JavaBeansTM server transparently
implements the necessary Remote Method Invocation (RMI) code so the client
program can reference the Enterprise Beans running on the server and access
them as if they are running locally to the client program.
Having RMI built into the Enterprise JavaBeans server is very convenient and saves
you coding time, but if you need to use advanced RMI features or integrate RMI
with an existing application, you need to override the default RMI
implementation and write your own RMI code.
This chapter replaces the container-managed RegistrationBean
from
Chapter 2: Entity and Session Beans with an RMI-based
registration server. The container-managed SellerBean
from Chapter 2
is also changed to call the new RMI registration server using a Java 2
RMI lookup
call.
About RMI
The RMI API lets you access a remote server object from a client program
by making simple method calls on the server object.
While other distributed architectures for accessing remote server objects such as
Distributed Component Object Model (DCOM) and Common Object Request Broker
Architecture (CORBA) return references to the remote object, the RMI API
not only returns references, but provides these additional benefits.
- The RMI API handles remote object references (call by reference) and
can also return a copy of the object (call by value).
- If the client program does not have local access to the class from
which a local or remote object was instantiated, RMI services can download
the class file.
Serialization and Data marshaling
To transfer objects, the RMI API uses the Serialization API to wrap (marshal)
and unwrap (unmarshal) the objects. To marshal an object, the
Serialization API converts the object to a stream of bytes, and to unmarshal
an object, the Serialization API converts a stream of bytes into an
object.
RMI over IIOP
One of the initial disadvantages to RMI was that its sole reliance on
the Java platform to write the interfaces made integration into existing
legacy systems difficult. However, RMI over Internet Inter-ORB Protocol
(IIOP) discussed in Chapter 4: Lookup Services
lets RMI communicate with any system or language that CORBA supports.
If you combine improved integration with the ability of RMI
to work through firewalls using HTTP firewall proxying, you might find
distributing your business logic using RMI is easier than
a socket-based solution.
Note:
Transfering code and data are key parts of the JiniTM System software
specification. In fact, adding a discovery and join service to the RMI services
would create something very similar to what you get in the Jini architecture.
RMI in the Auction Application
The RMI-based RegistrationServer
has the following new methods:
- A new
create
method for creating a new user.
- A new
find
method for finding a user.
- A new
search
method for the custom search of
users in the database.
The new custom search passes results back to the calling client by way
of an RMI callback. The RMI callback custom search is similar to the
finder
methods used in the Bean- and container-managed
examples from Chapters 2 and 3, except in the RMI version, it can take more
time to generate the results because the remote registration server calls a
remote method exported by the RMI-based
SellerBean
client.
If the calling client is written in the Java programming language, and is not,
for example, a web page, the server can update the client as soon as the results
are ready. But, the HTTP protocol used in most browsers does not allow results
to be pushed to the client without a request for those results. This means the
results web page is not created until the results are ready, which can add a small delay.
Class Overview
The two main classes in the RMI-based auction implementation are
SellerBean and the remote
RegistrationServer.
SellerBean
is called from
AuctionServlet
to insert an auction item into the database, and check for low
account balances.
The example models the Enterprise JavaBeans architecture in that a
user's registration details are separate from the code to create and find
the registration details. That is, the user's registration details provided
by the
Registration.java class
are separate from the code to create and find a Registration
object, which is in the
RegistrationHome.java
class.
The remote interface implementation in
RegistrationHome.java
is bound to the rmiregistry
. When a client program
wants to manipulate a user's registration details, it must first look
up the reference to the
RegistrationHome.java
object in the rmiregistry
.
File Summary
All the source code files for the RMI-based example are described
in the bullet list below.
You also need a
java.policy security policy
file to grant the permissions needed to run the example on
the Java 2 platforms.
Most RMI applications need the two socket permissions for
socket and HTTP access to the specified ports. The two thread permissions,
were listed in a stack trace as being needed for the
RegistrationImpl
class to create a new inner thread.
In the Java 2 platform, when a program does not have all the permissions
it needs, the Java1 virtual machine (VM) generates a stack trace that lists
the permissions that need to be added to the security policy file.
See Chapter 9: Program Signing and Security for more information.
grant {
permission java.net.SocketPermission
"*:1024-65535", "connect,accept,resolve";
permission java.net.SocketPermission "*:80",
"connect";
permission java.lang.RuntimePermission
"modifyThreadGroup";
permission java.lang.RuntimePermission
"modifyThread";
};
Compile the Example
Before describing the RMI-based code for the above classes, here
is the command sequence to compile the example on the Unix and Win32 platforms:
Unix:
javac registration/Registration.java
javac registration/RegistrationPK.java
javac registration/RegistrationServer.java
javac registration/ReturnResults.java
javac seller/SellerBean.java
rmic -d . registration.RegistrationServer
rmic -d . registration.RegistrationImpl
rmic -d . seller.SellerBean
Win32:
javac registration\Registration.java
javac registration\RegistrationPK.java
javac registration\RegistrationServer.java
javac registration\ReturnResults.java
javac seller\SellerBean.java
rmic -d . registration.RegistrationServer
rmic -d . registration.RegistrationImpl
rmic -d . seller.SellerBean
Start the RMI Registry
Because you are using your own RMI code, you have to explicitly start the
RMI Registry so the SellerBean
object can find the remote
Enterprise Beans. The RegistrationServer
uses the RMI
Registry to register or bind enterprise Beans that can be called remotely.
The SellerBean
client contacts the registry to look up and
get references to the remote AuctionItem
and Registration
Enterprise Beans.
Because RMI allows code and data to be transferred, you must be sure
the system classloader
does not load extra classes that
could be mistakenly sent to the client. In this example, extra classes
would be the Stub and Skel class files, and the RegistrationSever
and RegistrationImpl
classes, and to prevent them being
mistakenly sent, they should not appear anywhere in the CLASSPATH
when you start the RMI Registry. Because the current path could be included
automatically, you need to start the RMI Registry away from the code workspace too.
The following commands prevent the sending of extra classes
by unsetting the CLASSPATH
before starting the RMI
Registry on the default 1099 port. You can specify a different port by
adding the port number as follows: rmiregistry 4321 &
. If you
specify a different port number, you must specify the same port number in
both you client lookup
and server rebind
calls.
Unix:
export CLASSPATH=""
rmiregistry &
Win32:
unset CLASSPATH
start rmiregistry
Start the Remote Server
Once the rmiregistry
is running, you can start the
remote server, RegistrationServer
.
The RegistrationServer
program registers the name
registration2
with the rmiregistry
name server,
and any client can use this name to retrieve a reference to the
remote server object, RegistrationHome
.
To run the example, copy the RegistrationServer
and
RegistrationImpl
classes and the associated stub
classes a remotely accessible area and start the server program.
Unix:
cp *_Stub.class
/home/zelda/public_html/registration
cp RegistrationImpl.class
/home/zelda/public_html/registration
cd /home/zelda/public_html/registration
java -Djava.server.hostname=
phoenix.eng.sun.com RegistrationServer
Windows:
copy *_Stub.class
\home\zelda\public_html\registration
copy RegistrationImpl.class
\home\zelda\public_html\registration
cd \home\zelda\public_html\registration
java -Djava.server.hostname=
phoenix.eng.sun.com RegistrationServer
The following key properties used to configure RMI servers
and clients. These properties can be set inside the program or supplied
as command line properties to the Java VM.
- The
java.rmi.server.codebase
property specifies
where the publicly accessible classes are located. On the server this
can be a simple file URL to point to the directory or JAR file that
contains the classes. If the URL points to a directory, the URL must
terminate with a file separator character, "/".
If you are not using a file URL, you will either need an HTTP server
to download the remote classes or have to manually deliver the remote
client stub and remote interface classes in, for example, a JAR file.
- The
java.rmi.server.hostname
property is the
complete host name of the server where the publicly accessible
classes reside. This is only needed if the server has problems
generating a fully qualified name by itself.
- The
java.rmi.security.policy
property specifies
the policy file
with the permissions needed to run the remote server object and
access the remote server classes for download.
Establishing Remote Communications
Client programs communicate with each other through the
server. The server program consists of three files. The
Registration.java
and RegistrationHome.java
remote interface files define the methods that can
be called remotely, and the RegistrationServer.java
class file
defines the RegistrationServer
and RegistrationImpl
classes that implement the methods.
To establish remote communications, both the client and server programs
need to access the remote interface classes. The server needs the interface
classes to generate the interface implementation, and the client uses the
remote interface class to call the remote server method implementation.
For example, SellerBean
creates a reference to
RegistrationHome
, the interface, and not
RegistrationServer
, the implementation, when it needs to
create a user registration.
Besides the server interfaces and classes, you need stub and
skeleton classes to establish remote communications. The
stub and skeleton classes needed in this example are generated
when you run the rmic
compiler command on the
RegistrationServer
and SellerBean
classes.
The generated SellerBean
, SellerBean_Stub.class
and
SellerBean_Skel.class
classes are needed for the callback from the
server to the SellerBean
client. It is the _Stub.class
file on the client that marshals data to and unmarshals it from the server, while
the _Skel.class
class does the same for the server.
Note: In the Java 2 platform, the server side,
_Skel.class
file is no longer needed because its
function has been taken over by the Java Virtual Machine classes
Data Marshaling
Marshaling and unmarshaling data means that when you call the
RegistrationHome.create
method from SellerBean
,
this call is forwarded to the RegistrationServer_Stub.create
method. The RegistrationServer_Stub.create
method
wraps the method arguments and sends a serialized stream of bytes
to the RegistrationServer_Skel.create
method.
The RegistrationServer_Skel.create
method unwraps the
serialized bytestream, re-creates the arguments to the original
RegistrationHome.create
call, and returns the result of
calling the real RegistraionServer.create
method back along
the same route, but this time wrapping the data on the server side.
Marshaling and unmarshalling data is not without its complications.
The first issue is serialized objects might be incompatible across
Java Development Kit (JDKTM) releases. A Serialized object has an
identifier stored with the object that ties the serialized object to
its release. If the RMI client and server complain about incompatible
serial IDs, you might need to generate backward
compatible stubs and skeletons using the -vcompat
option
to the rmic
compiler.
Another issue is not all objects are serialized by default.
The initial Bean-managed RegistrationBean
object this
example is based on returns an Enumeration
object
that contains Registration
elements in a Vector
.
Returning this list from a remote method works fine, but when you
try to send a vector as a parameter to a remote method, you
get a runtime Marshaling
exception in the Java 2 platform.
Fortunately, in the Java 2 platform the Collections API offers
alternatives to previously unmarshable objects. In this example,
an ArrayList
from the Collections API replaces the
Vector
.
If the Collections API is not an option, you can create a wrapper class
that extends Serializable
and provides readObject
and writeObject
method implementations to convert the object into
a bytestream.
RegistrationServer Class
The
RegistrationServer class
extends java.rmi.server.UnicastRemoteObject
and implements
the create
, findByPrimaryKey
and
findLowCreditAccounts
methods declared in the
RegistrationHome
interface. The
RegistrationServer.java
source file also includes the implementation for the
Registration
remote interface as class
RegistrationImpl
. RegistrationImpl
also
extends UnicastRemoteObject
.
Exporting a Remote Object
Any object that you want to be remotely accessible needs to either extend
java.rmi.server.UnicastRemoteObject
or use the
exportObject
method from the UnicastRemoteObject
class. If you extend UnicastRemoteObject
, you also get
the equals
, toString
and hashCode
methods
for the exported object.
Passing by Value and Passing by Reference
Although the RegistrationImpl
class is not bound to the
registry, it is still referenced remotely because it is associated with
the RegistrationHome
return results. Because
RegistrationImpl
extends UnicastRemoteObject
, its
results are passed by reference, and so only one copy of that user's
registration Bean exists in the Java VM at any one time.
In the case of reporting results such as in the
RegistrationServer.findLowCreditAccounts
method, the RegistrationImpl
class copy of the remote object
could be used instead. By simply not extending UnicastRemoteObject
in the RegistrationImpl
class definition, a new Registration
object would be returned for each request. In effect
the values were passed but not the reference to the object on the server.
Distributed Garbage Collection
Using remote references to objects on the server from a client outside
the server's garbage collector introduces some potential problems with
memory leaks. How does the server know it is holding onto a
reference to a Registration
object that is no longer being used by any
clients because they aborted or a network connection was dropped?
To avoid potential memory leaks on the server from clients, RMI uses a leasing
mechanism when giving out references to exported objects. When exporting
an object, the Java VM increases the count for the number of references to this
object and sets an expiration time, or lease time, for the new reference to this
object.
When the lease expires, the reference count of this object is decreased
and if it reaches 0, the object is set for garbage collection by the Java VM.
It is up to the client that maintains this weak reference to the remote
object to renew the lease if it needs the object beyond the lease time.
A weak reference is a way to refer to an object in memory without
keeping it from being garbage collected.
This lease time value is a configurable property measured in milliseconds. If
you have a fast network, you could shorten the default value and create a large
number of transient object references.
The following code sets the lease timeout to 2 minutes.
Property prop = System.getProperties();
prop.put("java.rmi.dgc.leaseValue", 120000);
The create
and findByPrimaryKey
methods
are practically identical to the other versions of the Registration Server.
The main difference is that on the server side, the registration record
is referenced as RegistrationImpl
, which is the implementation
of Registration
. On the client side, Registration
is used instead.
The findLowCreditAccounts
method builds an
ArrayList
of serializable RegistrationImpl
objects
and calls a remote method in the SellerBean
class to pass the
results bacl. The results are generated by an inner Thread
class
so the method returns before the results are complete. The SellerBean
object waits for the updateAccounts
method to be called before displaying
the HTML page. In a client written with the Java programming langauge,
it would not need to wait, but could display the update in real time.
public class RegistrationServer
extends UnicastRemoteObject
implements RegistrationHome {
public registration.RegistrationPK
create(String theuser,
String password,
String emailaddress,
String creditcard)
throws registration.CreateException{
// code to insert database record
}
public registration.Registration
findByPrimaryKey(registration.RegistrationPK pk)
throws registration.FinderException {
if ((pk == null) || (pk.getUser() == null)) {
throw new FinderException ();
}
return(refresh(pk));
}
private Registration refresh(RegistrationPK pk)
throws FinderException {
if(pk == null) {
throw new FinderException ();
}
Connection con = null;
PreparedStatement ps = null;
try{
con=getConnection();
ps=con.prepareStatement("select password,
emailaddress,
creditcard,
balance from registration where theuser = ?");
ps.setString(1, pk.getUser());
ps.executeQuery();
ResultSet rs = ps.getResultSet();
if(rs.next()) {
RegistrationImpl reg=null;
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = pk.getUser();
reg.password = rs.getString(1);
reg.emailaddress = rs.getString(2);
reg.creditcard = rs.getString(3);
reg.balance = rs.getDouble(4);
return reg;
}else{
throw new FinderException ();
}
}catch (SQLException sqe) {
throw new FinderException();
}finally {
try{
ps.close();
con.close();
}catch (Exception ignore) {}
}
}
public void findLowCreditAccounts(
final ReturnResults client)
throws FinderException {
Runnable bgthread = new Runnable() {
public void run() {
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
ArrayList ar = new ArrayList();
try{
con=getConnection();
ps=con.prepareStatement("select theuser,
balance from registration
where balance < ?");
ps.setDouble(1, 3.00);
ps.executeQuery();
rs = ps.getResultSet();
RegistrationImpl reg=null;
while (rs.next()) {
try{
reg= new RegistrationImpl();
}catch (RemoteException e) {}
reg.theuser = rs.getString(1);
reg.balance = rs.getDouble(2);
ar.add(reg);
}
rs.close();
client.updateResults(ar);
}catch (Exception e) {
System.out.println("findLowCreditAccounts: "+e);
return;
}
finally {
try{
if(rs != null) {
rs.close();
}
if(ps != null) {
ps.close();
}
if(con != null) {
con.close();
}
}catch (Exception ignore) {}
}
} //run
};
Thread t = new Thread(bgthread);
t.start();
}
}
The main
method loads the JDBCTM pool driver. This
version uses the Postgres database, installs the
RMISecurityManager
, and contacts the RMI registry to
bind the the RegistrationHome
remote object to
the name registration2
. It does not need to bind the
remote interface, Registration
because that
class is loaded when it is referenced by RegistrationHome
.
By default, the server name uses port 1099. If you
want to use a different port number, you can add it
with a colon as follows: kq6py:4321
.
If you change the port here, you must start the
RMI Registry with the same port number.
The main
method also installs a RMIFailureHandler
.
If the server fails to create a server socket then the failure handler
returns true
which instructs the RMI server to retry the
operation.
public static void main(String[] args){
try {
new pool.JDCConnectionDriver(
"postgresql.Driver",
"jdbc:postgresql:ejbdemo",
"postgres", "pass");
} catch (Exception e){
System.out.println(
"error in loading JDBC driver");
System.exit(1);
}
try {
Properties env=System.getProperties();
env.put("java.rmi.server.codebase",
"http://phoenix.eng.sun.com/registration");
RegistrationServer rs=
new RegistrationServer();
if (System.getSecurityManager() == null ) {
System.setSecurityManager(
new RMISecurityManager());
}
RMISocketFactory.setFailureHandler(
new RMIFailureHandlerImpl());
Naming.rebind("
//phoenix.eng.sun.com/registration2",rs);
}catch (Exception e) {
System.out.println("Exception thrown "+e);
}
}
}
class RMIFailureHandlerImpl
implements RMIFailureHandler {
public boolean failure(Exception ex ){
System.out.println("exception "+ex+" caught");
return true;
}
}
Registration Interface
The Registration interface declares the
methods implemented by RegistrationImpl
in the
RegistrationServer.java
source file.
package registration;
import java.rmi.*;
import java.util.*;
public interface Registration extends Remote {
boolean verifyPassword(String password)
throws RemoteException;
String getEmailAddress() throws RemoteException;
String getUser() throws RemoteException;
int adjustAccount(double amount)
throws RemoteException;
double getBalance() throws RemoteException;
}
RegistrationHome Interface
The RegistrationHome interface
declares the methods implemented by theRegistrationServer
class.
These methods mirror the Home interface defined in the Enterprise JavaBeans
example. The findLowCreditAccounts
method takes a remote interface
as its only parameter.
package registration;
import java.rmi.*;
import java.util.*;
public interface RegistrationHome extends Remote {
RegistrationPK create(String theuser,
String password,
String emailaddress,
String creditcard)
throws CreateException,
RemoteException;
Registration findByPrimaryKey(RegistrationPK theuser)
throws FinderException, RemoteException;
public void findLowCreditAccounts(ReturnResults rr)
throws FinderException, RemoteException;
}
ReturnResults Interface
The ReturnResults interface declares the
method implemented by the SellerBean
class. The
updateResults
method is called from RegistrationServer
.
package registration;
import java.rmi.*;
import java.util.*;
public interface ReturnResults extends Remote {
public void updateResults(ArrayList results)
throws FinderException, RemoteException;
}
SellerBean Class
The SellerBean class includes
the callback method implementation and calls the RegistrationServer
object using RMI. The updateAccounts
method is made accessible by
a call to UnicastRemoteObject.exportObject(this);
. The
auditAccounts
method waits on a Boolean
object.
The updateAccounts
method sends a notify to all methods
waiting on the Boolean
object when it has been called from the server
and receives the search results.
package seller;
import java.rmi.RemoteException;
import java.rmi.*;
import javax.ejb.*;
import java.util.*;
import java.text.NumberFormat;
import java.io.Serializable;
import javax.naming.*;
import auction.*;
import registration.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
public class SellerBean
implements SessionBean, ReturnResults {
protected SessionContext ctx;
javax.naming.Context ectx;
Hashtable env = new Hashtable();
AuctionServlet callee=null;
Boolean ready=new Boolean("false");
ArrayList returned;
public int insertItem(String seller,
String password,
String description,
int auctiondays,
double startprice,
String summary)
throws RemoteException {
try{
RegistrationHome regRef = (
RegistrationHome)Naming.lookup(
"//phoenix.eng.sun.com/registration2");
RegistrationPK rpk= new RegistrationPK();
rpk.setUser(seller);
Registration newseller = (
Registration)regRef.findByPrimaryKey(rpk);
if((newseller == null) ||
(!newseller.verifyPassword(password))) {
return(Auction.INVALID_USER);
}
AuctionItemHome home = (
AuctionItemHome) ectx.lookup(
"auctionitems");
AuctionItem ai= home.create(seller,
description,
auctiondays,
startprice,
summary);
if(ai == null) {
return Auction.INVALID_ITEM;
}else{
return(ai.getId());
}
}catch(Exception e){
System.out.println("insert problem="+e);
return Auction.INVALID_ITEM;
}
}
public void updateResults(java.util.ArrayList ar)
throws RemoteException {
returned=ar;
synchronized(ready) {
ready.notifyAll();
}
}
public ArrayList auditAccounts() {
this.callee=callee;
try {
RegistrationHome regRef = (
RegistrationHome)Naming.lookup(
"//phoenix.eng.sun.com/registration2");
regRef.findLowCreditAccounts(this);
synchronized(ready) {
try {
ready.wait();
} catch (InterruptedException e){}
}
return (returned);
}catch (Exception e) {
System.out.println("error in creditAudit "+e);
}
return null;
}
public void ejbCreate()
throws javax.ejb.CreateException,
RemoteException {
env.put(
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.TengahInitialContextFactory");
try{
ectx = new InitialContext(env);
} catch (NamingException e) {
System.out.println(
"problem contacting EJB server");
throw new javax.ejb.CreateException();
}
Properties env=System.getProperties();
env.put("java.rmi.server.codebase",
"http://phoenix.eng.sun.com/registration");
env.put("java.security.policy","java.policy");
UnicastRemoteObject.exportObject(this);
}
public void setSessionContext(SessionContext ctx)
throws RemoteException {
this.ctx = ctx;
}
public void unsetSessionContext()
throws RemoteException {
ctx = null;
}
public void ejbRemove() {}
public void ejbActivate() throws RemoteException {
System.out.println("activating seller bean");
}
public void ejbPassivate() throws RemoteException {
System.out.println("passivating seller bean");
}
}
_______
1 As used on this web site,
the terms "Java virtual
machine" or "JVM" mean a virtual machine
for the Java platform.
[TOP]