RMI-IIOP Programmer's Guide
Copyright © 1999 Sun Microsystems, Inc.
Copyright © 1999 International Business Machines
Corporation. All Rights Reserved. |
Table of Contents
Introduction
This document will teach you how to write Java Remote Method Invocation
(RMI) programs that can access remote objects by using the Internet Inter-ORB
Protocol (IIOP). By making your RMI programs conform to a small set of
restrictions, your RMI programs can access CORBA objects. RMI-IIOP gives
you RMI ease-of-use coupled with CORBA/IIOP language interoperability.
This document is for RMI programmers who want to write RMI-IIOP programs,
or convert existing RMI programs to RMI-IIOP.
Background Reading
Here are some sites to get you up to speed with this technology:
-
The RMI-IIOP
home page contains links to RMI-IIOP documentation, sample code, specifications,
news, other related web sites, and more. Check it out!
-
The RMI-IIOP FAQ
page answers many basic RMI-IIOP questions.
-
The Java RMI
home page contains links to RMI documentation, examples, specification,
and more. Make sure you read this.
-
The RMI trail in the Java Tutorial.
-
The JDK 1.3
RMI javadoc contains the most up-to-date RMI API documentation.
-
The Java
IDL web page will familiarize you with Sun's CORBA/IIOP implementation.
-
The Java IDL trail in the Java Tutorial.
What is RMI-IIOP?
RMI
With RMI you can write distributed programs in the Java programming language.
RMI is easy to use, you don't need to learn a separate interface definition
language (IDL), and you get Java's inherent "write once, run anywhere"
benefit. Clients, remote interfaces, and servers are written entirely in
Java. RMI uses the Java Remote Method Protocol (JRMP) for remote Java object
comunication. To get a quick introduction to writing RMI programs, see
the RMI
Tutorial web page. That document describes writing a simple "Hello
World" RMI program.
RMI lacks interoperability with other languages, and, because it uses
a non-standard communication protocol, cannot communicate with CORBA objects.
IIOP, CORBA, and Java IDL
IIOP is CORBA's communication protocol. It defines the way the bits are
sent over a wire between CORBA clients and servers. CORBA is a standard
distributed object architecture developed by the Object Management Group
(OMG). Interfaces to remote objects are described in a platform-neutral
interface definition language (IDL). Mappings from IDL to specific programming
languages are implemented, binding the language to CORBA/IIOP.
The JDK's CORBA/IIOP implementation is known as Java IDL. Along with
the idltojava compiler, Java IDL can be used to define, implement,
and access CORBA objects from the Java programming language.
The Java
IDL web page gives you a good, Java-centric view of CORBA/IIOP programming.
To get a quick introduction to writing Java IDL programs, see the Getting
Started: Hello World web page.
RMI-IIOP
Previously Java programmers had to choose between RMI and CORBA/IIOP (Java
IDL) for distributed programming solutions. Now, by adhering to a few restrictions,
RMI objects can use the IIOP protocol, and communicate with CORBA objects.
This solution is known as RMI-IIOP. RMI-IIOP combines RMI-style ease of
use with CORBA cross-language interoperability.
The New rmic Compiler
The RMI-IIOP software comes with a new rmic compiler that can
generate IIOP stubs and ties, and emit IDL.
Here are the new rmic flags:
-
-iiop
-
Generates IIOP stubs/ties
-
-idl
-
Generates IDL
-
-noValueMethods
-
Stops generation of IDL for methods and constructors within IDL valuetypes
-
-always
-
Forces re-generation even when existing stubs/ties/idl are newer than the
input class. Only valid when -iiop and/or -idl flags are present.
-
-idlModule <fromJavaPackage<.class>>
<toIDLModule>
-
Specifies IDLEntity package mapping. For example:
-idlModule foo.bar my::real::idlmod
-
-idlFile <fromJavaPackage<.class>> <toIDLFile>
-
Specifies IDLEntity file mapping. For example:
-idlFile test.pkg.X TEST16.idl
The new rmic behaves differently than previous versions when no
output directory (-d option) is specified. Previously, the stub
and tie files were always written into the current working directory when
no -d option is specifed, regardless of package. The new
rmic
writes the files into subdirectories of the current directory that correspond
to their packages.
The -iiop Flag
Using rmic with the -iiop option generates stub and tie
classes. A stub class is a local proxy for a remote object. Stub classes
are used by clients to send calls to a server. Each remote interface requires
a stub class, which implements that remote interface. The client's reference
to a remote object is actually a reference to a stub. Tie classes are used
on the server side to process incoming calls, and dispatch the calls to
the proper implementation class. Each implementation class requires a tie
class.
Stub classes are also generated for abstract interfaces. An abstract
interface is an interface that does not extend java.rmi.Remote,
but whose methods all throw either java.rmi.RemoteException or
a superclass of java.rmi.RemoteException. Interfaces that do not
extend
java.rmi.Remote and have no methods are also abstract interfaces.
The -idl Flag
Using rmic with the -idl option generates OMG IDL for
the classes specified and any classes referenced.
IDL provides a purely declarative, programming language independent
means for specifying the API for an object.
The IDL is used as a specification for methods and data that can be
written in and invoked from any language that provides CORBA bindings.
This includes Java and C++ among others. See the Java
Language to IDL Mapping (OMG) document for a complete description.
Note: The generated IDL can only be compiled using an IDL compiler
that supports the CORBA 2.3 extensions to IDL.
The -noValueMethods Flag
The -noValueMethods option, when used with -idl, ensures
that methods and initializers are not included in valuetypes
emitted during IDL Generation. These are optional for valuetypes
and are otherwise omitted.
See the RMIC
tool page for a complete
rmic description.
The New idlj Compiler
The RMI-IIOP software includes a new IDL-to-Java compiler. This compiler
supports the new CORBA Objects By Value feature, which is required for
interoperation with RMI-IIOP. It is written in Java, and so can run on
any platform. See the
IDL-to-Java Compiler
User's Guide for details of how to use this compiler.
How to Make RMI Programs Use IIOP
The following steps are a general guide to converting an RMI application
to RMI-IIOP.
-
If you are using the RMI registry for naming services, you need
to switch to JNDI with the CosNaming plugin. You need to do the
following:
-
In both your client and server code, you need to create an InitialContext
for JNDI using the following code:
import javax.naming.*;
...
Context initialNamingContext = new InitialContext();
-
Modify all uses of RMI registry lookup(), bind(), and
rebind()
to use JNDI lookup(), bind(), and rebind() instead.
For example, instead of your RMI server using:
import java.rmi.*;
...
Naming.rebind("MyObject", myObj);
use:
import javax.naming.*;
...
initialNamingContext.rebind("MyObject", myObj);
-
If the client is an applet, the client applet needs to pass this
to the JNDI CosNaming plugin. Replace the above code with the
following:
import java.util.*;
import javax.naming.*;
...
Hashtable env = new Hashtable();
env.put("java.naming.applet", this);
Context ic = new InitialContext(env);
-
If you are not using the RMI registry for naming services, you have
some other way of bootstrapping your initial remote object reference. For
example, your server code may be using Java serialization to write an RMI
object reference to an ObjectOutputStream and passing this to
your client code for deserializing into an RMI stub. When doing this in
RMI-IIOP, it is also necessary to ensure that object references are connected
to an ORB before serialization and after deserialization. The following
code example shows how to do this.
On the server side, use the PortableRemoteObject.toStub() call
to obtain a stub, then use writeObject() to serialize this stub
to an ObjectOutputStream. If necessary, use
Stub.connect()
to connect the stub to an ORB before serializing it. The code to do this
looks something like:
org.omg.CORBA.ORB myORB = org.omg.CORBA.ORB.init(new String[0], null);
Wombat myWombat = new WombatImpl();
javax.rmi.CORBA.Stub myStub = (javax.rmi.CORBA.Stub)PortableRemoteObject.toStub(myWombat);
myStub.connect(myORB);
// myWombat is now connected to myORB. To connect other objects to the
// same ORB, use PortableRemoteObject.connect(nextWombat, myWombat);
FileOutputStream myFile = new FileOutputStream("t.tmp");
ObjectOutputStream myStream = new ObjectOutputStream(myFile);
myStream.writeObject(myStub);
On the client side, use readObject() to deserialize a remote reference
to the object from an ObjectInputStream. Before using the deserialized
stub to call remote methods, it must be connected to an ORB. You could
do this with code like:
FileInputStream myFile = new FileInputStream("t.tmp");
ObjectInputStream myStream = new ObjectInputStream(myFile);
Wombat myWombat = (Wombat)myStream.readObject();
org.omg.CORBA.ORB myORB = org.omg.CORBA.ORB.init(new String[0], null);
((javax.rmi.CORBA.Stub)myWombat).connect(myORB);
// myWombat is now connected to myORB. To connect other objects to the
// same ORB, use PortableRemoteObject.connect(nextWombat, myWombat);
As you can see, the JNDI approach is much simpler, so use it whenever possible.
-
Either change your remote implementation classes to inherit from javax.rmi.PortableRemoteObject,
or explicitly export implementation objects after creation by calling PortableRemoteObject.exportObject().
-
Change all the places in your code where there is a Java cast of a remote
interface to use javax.rmi.PortableRemoteObject.narrow().
-
Don't depend on distributed garbage collection or use any of the RMI DGC
facilities. Use PortableRemoteObject.unexportObject() to unexport
objects that are no longer in use.
-
Regenerate the RMI stubs and ties using the rmic command with
the -iiop option. This will produce stub and tie files with the
following names:
_<implementationName>_Tie.class
_<interfaceName>_Stub.class
-
Before starting the server, start the CosNaming server (in its
own process) using the following command:
tnameserv
This uses the default port number of 900. If you want to use a different
port number, use the following command:
tnameserv -ORBInitialPort 1050
-
When starting client and server applications, specify the following system
properties:
java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://<hostname>:900
<appl_class>
This example uses the default name service port number of 900. If you specified
a different port in step 8, you need to use the same port number in the
provider URL here. The <hostname> in the provider URL is the host name
that was used to start the CosNaming server in step 7.
-
If the client is an applet, specify the following properties in the applet
tag:
java.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
java.naming.provider.url=iiop://<hostname>:900
This example uses the default name service port number of 900. If you specified
a different port in step 8, you need to use the same port number in the
provider URL here. The <hostname> in the provider URL is the host name
that was used to start the CosNaming server in step 7.
Restrictions When Running RMI Programs Over
IIOP
To make existing RMI programs run over IIOP, you need to observe the following
restrictions.
-
Make sure all constant definitions in remote interfaces are of primitive
types or String and evaluated at compile time.
-
Don't use Java names that conflict with IDL mangled names generated by
the Java to IDL mapping rules. See section 28.3.2 of the Java
Language to IDL Mapping specification for the Java to IDL name mapping
rules.
-
Don't inherit the same method name into a remote interface more than once
from different base remote interfaces.
-
Be careful when using names that differ only in case. The use of a type
name and a variable of that type whose name differs from the type name
only in case is supported. Most other combinations of names that differ
only in case are not supported.
-
Don't depend on runtime sharing of object references to be preserved exactly
when transmitting object references across IIOP. Runtime sharing of other
objects is preserved correctly.
-
Don't use the following features of RMI:
-
RMISocketFactory
-
UnicastRemoteObject
-
Unreferenced
-
The DGC interfaces
Converting the RMI Hello World Program to RMI-IIOP
In the following example you'll convert the RMI
Hello World example to RMI-IIOP. You will also convert RMI Hello World
applet client to an application.
Here's the RMI Hello World players:
-
HelloImpl.java is the RMI server.
-
Hello.java is the remote interface implemented by HelloImpl.
-
HelloApplet.java is the RMI client.
-
Hello.html loads HelloApplet.
The RMI Hello World example uses a development directory of $HOME/mysrc/examples/hello
and a deployment directory of $HOME/public_html/myclasses, where
$HOME
is your home directory. Though you don't have to, the following example
assumes that you use these directories.
If you haven't already, go through the RMI Hello World example. Once
you've completed this example, take the following steps.
Adapt the Implementation Class (Server) to RMI-IIOP:
-
Import javax.rmi.PortableRemoteObject rather than java.rmi.server.UnicastRemoteObject:
//Goodbye
//import java.rmi.server.UnicastRemoteObject;
//Hello
import javax.rmi.PortableRemoteObject;
-
Import the JNDI naming package:
import javax.naming.*;
-
Make HelloImpl extend PortableRemoteObject rather than
UnicastRemoteObject:
public class HelloImpl
extends PortableRemoteObject
...
-
Use the JNDI registry, rather than the RMI registry, by adding the following
code:
Context initialNamingContext = new InitialContext();
This step provides an initial JNDI naming context (and will also need to
be done in the client).
-
Use JNDI rebind(), rather than the RMI version:
Old code:
HelloImpl obj = new HelloImpl("HelloServer");
Naming.rebind("HelloServer", obj);
New code:
HelloImpl obj = new HelloImpl("HelloServer"); //unchanged
initialNamingContext.rebind("HelloServer",obj);
Here are the changes you need to make to HelloApplet.java:
-
Import the PortableRemoteObject package:
import javax.rmi.PortableRemoteObject;
-
Create a JNDI initial naming context, and pass this to the CosNaming plugin:
import java.util.*;
import javax.naming.*;
...
Hashtable env = new Hashtable();
env.put("java.naming.applet", this);
Context ic = new InitialContext(env);
-
Use JNDI lookup(), rather than the RMI version, AND replace
the Java remote interface cast with a call to javax.rmi.PortableRemoteObject.narrow():
Old code:
import java.rmi.*;
...
Hello obj = (Hello)Naming.lookup("//" +
getCodeBase().getHost() + "/HelloServer");
New code:
import javax.naming.*;
...
Hello obj =
(Hello)PortableRemoteObject.narrow(
initialNamingContext.lookup("HelloServer"),
Hello.class);
Specify Naming Properties in the Applet Tag
Add the following properties to Hello.html applet tag:
<param name="java.naming.factory.initial" value="com.sun.jndi.cosnaming.CNCtxFactory">
<param name="java.naming.provider.url" value="iiop://<hostname>:900">
Compile the Java Source Files
javac -d $HOME/public_html/myclasses Hello.java HelloImpl.java HelloApplet.java
Generate the Stub and Tie Classes
rmic -iiop -d $HOME/public_html/myclasses examples.hello.HelloImpl
This will generate the files _Hello_Stub.class (the client-side
proxy) and _HelloImpl_Tie.class (the server-side proxy) in the
$HOME/public_html/myclasses/examples/hello
directory.
Start the Name Server
tnameserv
This starts the CosNaming name server with the default port of 900. If
you want to use a different port number, use a command line such as:
tnameserv -ORBInitialPort 1050
Start the Hello Server
java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://<hostname>:900
examples.hello.HelloImpl
Start the Hello Client
Use appletviewer to load Hello.html.
appletviewer Hello.html
As a reminder, here's what Hello.html looks like:
<html>
<title>Hello World</title>
<center> <h1>Hello World</h1> </center>
The message from the HelloServer is:
<p>
<applet
code="examples.hello.HelloApplet"
width=500 height=120>
<param name="java.naming.factory.initial" value="com.sun.jndi.cosnaming.CNCtxFactory">
<param name="java.naming.provider.url" value="iiop://<hostname>:900">
</applet>
</HTML>
If all goes according to plan, appletviewer will echo the HelloServer's
message.
Converting the Client Applet to an Application
Here's how to change the applet client to an application client:
Adapt the Client Application to RMI-IIOP:
-
Convert HelloApplet to an application:
-
Copy HelloApplet.java (the original RMI version) to HelloApp.java.
-
Change the class name (for example, to HelloApp).
-
Remove the extends Applet clause.
-
Change init() to main().
-
Move String message ="";.
-
Eliminate the paint() method.
-
Use the JNDI registry, rather than the RMI registry:
import javax.naming.*;
...
Context initialNamingContext = new InitialContext();
-
Use JNDI lookup(), rather than the RMI version, AND replace
the Java remote interface cast with a call to javax.rmi.PortableRemoteObject.narrow():
Old code:
import java.rmi.*;
...
Hello obj = (Hello)Naming.lookup("//" +
getCodeBase().getHost() + "/HelloServer");
New code:
import javax.naming.*;
...
Hello obj = (Hello)PortableRemoteObject.narrow(
initialNamingContext.lookup("HelloServer"),
Hello.class);
The host and port will be designated when starting the server.
Compile the HelloApp Source
javac -d $HOME/public_html/myclasses HelloApp.java
You don't need to regenerate your stub and tie.
Start the Name Server and Hello Server
Start these the same as in the applet example.
Start the Hello Application Client
Here's how:
java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://<hostname>:900
examples.hello.HelloApp
You'll see the server's message printed to the client console.
Other Things You Should Know
Servers Need to be Thread Safe
Since remote method invocations on the same remote object may execute concurrently,
a remote object implementation must be thread-safe.
Hashtables with Identical Vector Keys
When a hashtable containing identical vector keys is passed from a JDK
1.1 RMI application to a JDK 1.2 RMI application (using either IIOP or
JRMP), the identical keys are "coalesced" into one key because of JDK 1.2
deserialization rules. For example:
-
A 1.1 RMI application creates a hashtable.
-
The application puts a value into the hashtable using a vector key A.
-
The application puts another value into the hashtable using a vector key
B. Vector B is structurally identical to vector A, but is a different object.
-
The hashtable now has two entries with keys A and B.
-
The 1.1 RMI application sends the hashtable, by value, to a 1.2 RMI application.
-
The hashtable on the 1.2 side has one entry with key B. This is because
the deserialization code for the hashtable uses 1.2 rules to populate it,
and these rules compare vector keys by value instead of by object identity.
-
If the hashtable is ever sent back across RMI from 1.2 to 1.1, it will
still have one entry with key B.