New York
University
Computer
Science Department
Courant
Institute of Mathematical Sciences
RMI-IIOP
Example
(JavaSoft Whitepaper Adapted for the Course)
Course Title: Application Servers Course Number: g22.3033-011
Instructor: Jean-Claude Franchitti Session: 4
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--> <!-- 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.
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.
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
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
<!--
<p>
The <code>-iiop</code> flag
<i>cannot</i> be combined with any of
the JRMP flags
(<code>-v1.1</code>, <code>-vcompat</code>,
<code>-v1.2</code>)
because JRMP and IIOP use the same name
for stub files (for example,
<code>Foo</code> becomes <code>Foo_Stub</code>).
So the last generator would
overwrite the previous one's output.
<p>
When none of the "generator"
flags is specified (<code>-iiop</code>,
<code>-idl</code>,
<code>-v1.1</code>, <code>-vcompat</code>,
<code>-v1.2</code>),
<code>rmic</code> defaults to one of two
JRMP choices depending on the JDK version:
If JDK 1.2 is being used, the default is
<code>-vcompat</code>, else the default
is <code>-v1.1</code>.
<p>
Note that choosing
<code>-vcompat</code> or <code>-v1.2</code> when
running on JDK 1.1.6 will result in an
error. Without this behavior,
the generated code would fail to compile,
resulting in an error that's
not very useful.
-->
The
new rmic behaves differently than
previous versions when no output directory (-d option) is specified. In
the JDK, the stub and tie files are always written into the current working
directory when no -d option is specifed,
regardless of package. Here is a description of the new rmic behavior:
· For each input class which has source that must be compiled, .class files are created in a directory chosen as follows:
1.
If
the -d option is present, use specified directory as the root, creating
subdirectories as needed; else...
2.
If
the source file is not zipped, use the directory containing the source file;
else...
3.
Exit
with "can't write" error.
· For each input class, all generated files (.idl and/or _Stub/_Tie/_Skel and their .class files) are created in a directory chosen as follows:
1.
If
the -d option is present, use specified directory as the root, creating subdirectories
as needed; else...
2.
Search
the classpath for the input class file. If found and not zipped, use the
directory containing the class file; else...
3.
Search
the classpath for the input class source file. If found and not zipped, use the
directory containing the source file; else...
4.
Search
the classpath for an existing subdirectory whose relative path matches the
package of the input class. If found and not zipped, use it; else...
5.
If
the input class has no package, use the current working directory (from the
System property user.dir); else...
6.
If
the current working directory is in the classpath, use it as the root, creating
subdirectories as needed; else...
7.
Exit
with an error message which says that the -d option is required.
<!--
There is one other flag that we might want
to document. It is only valid when
-iiop and/or -idl flags are present:
<blockquote>
-always Forces re-generation even when existing stubs/ties/idl is newer
than
the input class.
</blockquote>
This one may never be needed by most users.
I use it when I change rmic itself
and need to ensure that stubs are
re-generated. I did not include this one in
the usage message that rmic dumps.
<p>
-->
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.
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.
The -noValueMethods Flag
The
-noValueMethods option, when used with -idl, ensures that methods and
initializers are notincluded in valuetypes emitted during IDL
Generation. These are optional for valuetypes and are otherwise omitted.
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. <!-- The RMI/IIOP COOKBOOK -->
How to Make RMI
Programs Use IIOP
The
following steps are a general guide to converting an RMI application to
RMI-IIOP.
1.
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:
a.
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();
b.
Modify
all uses of RMI registry lookup() and bind() to use JNDI lookup() and bind()instead. For example,
instead of your RMI server using:
import
java.rmi.*;
...
Naming.rebind("MyObject", myObj);
use:
import
javax.naming.*;
...
initialNamingContext.rebind("MyObject", myObj);
c.
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);
2.
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.
On the server side, use the PortableRemoteObject.toStub() call to obtain a stub, then
use writeObject() to serialize this stub to
an ObjectOutputStream. 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.Util.write_RemoteObject(myORB.create_output_stream(),
myWombat);
// 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(PortableRemoteObject.toStub(myWombat));
On the client side, use readObject() to deserialize a remote
reference to the object from an ObjectInputStream, 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);
You will also need to
specify the -nolocalstubs option with the rmic -iiop command.
3.
Either
change your remote implementation classes to inherit from javax.rmi.PortableRemoteObject, or explicitly export
implementation objects after creation by calling PortableRemoteObject.exportObject().
4.
Change
all the places in your code where there is a Java cast of a remote interface to
use javax.rmi.PortableRemoteObject.narrow().
5.
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. This has no effect for objects exported to JRMP on 1.1.6.
6.
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:
_<implementionName_Tie.class
_<interfaceName_Stub.class
7.
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
The CLASSPATH must have
previously been modified as necessary. Alternatively, the settings described in
the installation instructions can be passed on the command line using the -classpath option. If the -classpath approach is used on JDK
1.1, the classes.zip file from the JDK must also
be specified as the last item in the classpath.
8.
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 8.
The CLASSPATH must have
previously been modified as necessary. Alternatively, the settings described in
the installation instructions can be passed on the command line using the -classpath option. If the -classpath approach is used on JDK
1.1, the classes.zip file from the JDK must also
be specified as the last item in the classpath.
9.
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 8.
Restrictions
When Running RMI Programs Over IIOP
To
make existing RMI programs run over IIOP, you need to observe the following
restrictions.
1.
Make
sure all constant definitions in remote interfaces are of primitive types or
String and evaluated at compile time.
2.
Don't
use Java names that conflict with IDL mangled names generated by the Java to
IDL mapping rules. See section 26.4.2 of the Java Language to IDL Mapping specification for the Java to IDL name
mapping rules.
3.
Don't
inherit the same method name into a remote interface more than once from
different base remote interfaces.
4.
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.
5.
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.
6.
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 developement directory of $HOME/jdk1.1/mysrc/example/hello and a deployment directory
of $HOME/public_html/codebase, 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:
1.
Import
javax.rmi.server.PortableRemoteObject rather than javax.rmi.server.UnicastRemoteObject:
//Goodbye
//import
java.rmi.server.UnicastRemoteObject;
//Hello
import
javax.rmi.PortableRemoteObject;
2.
Import
the JNDI naming package:
import javax.naming.*;
3.
Make
HelloImpl extend PortableRemoteObject rather than UnicastRemoteObject:
public class HelloImpl
extends PortableRemoteObject
...
4.
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).
5.
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:
1.
Import
the PortableRemoteObject package:
import
javax.rmi.PortableRemoteObject;
<!-- ORIGINAL STEP TWO
<li><b>(Current Release)</b> Create a JNDI
(<code>javax.naming</code>)
initial naming context, and start the ORB explicitly:
<pre>
import java.util.*;
import javax.naming.*;
import org.omg.CORBA.ORB;
...
Hashtable env = new Hashtable();
ORB orb = ORB.init(this, null);
env.put("java.naming.corba.orb", orb);
//The next two values will be specifiable
//By applet tag params in future releases
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://<hostname>:900");
Context initialNamingContext = new InitialContext(env);
</pre>
<b>(Future Releases)</b> Create a JNDI initial naming
context,
and pass <code>this</code> to the
<code>CosNaming</code>
plugin:
<pre>
import java.util.*;
import javax.naming.*;
...
Hashtable env = new Hashtable();
env.put("java.naming.corba.applet", this);
Context ic = new InitialContext(env);
</pre>
--><!-- NEW STEP TWO -->
2.
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.corba.applet", this);
//The next two values will be
specifiable
//By applet tag params in future
releases
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://<hostname:900");
Context ic = new
InitialContext(env);
3.
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"
Note:
Setting these parameters in the applet tag doesn't currently work. See the
readme.
Compile the Java Source
Files
javac -d $HOME/public_html/codebase Hello.java
HelloImpl.java HelloApplet.java
Generate the Stub and Tie
Classes
Make
sure that your search path finds the rmic command in the $RMI_IIOP_HOME/bin directory.
rmic -iiop -d $HOME/public_html/codebase
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/codebase/examples/hello directory.
Start the JNDI Name Server
tnameserv
This
starts the JNDI name server with the default port of 900. If you want to use a
different port number (Solaris users must use a port above 1024), use a command
line like:
tnameserv -ORBInitialPort 1050
Make
sure your CLASSPATH is set, as necessary.
Start the Hello Server
java
-Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://<hostname:900
examples.hello.HelloImpl
Make
sure your CLASSPATH is set, as necessary.
Start the Hello Client
Use
appletviewer to load Hello.html.
appletviewer Hello.html
As
a reminder, here's what Hello.html looks like:
<html
<titleHello World</title
<center <h1Hello World</h1 </center
The message from the HelloServer is:
<p
<applet
code="examples.hello.HelloApplet"
width=500
height=120
</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:
1.
Convert
HelloApplet to an application:
a.
Copy HelloApplet.java (the original RMI version)
to HelloApp.java
b.
Change
the class name (for example, to HelloApp)
c.
Remove
the extends Applet clause
d.
Change init() to main()
e.
Move String
message ="";
f.
Eliminate
the paint() method
2.
Use
the JNDI registry, rather than the RMI registry:
import javax.naming.*;
...
Context initialNamingContext = new
InitialContext();
3.
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/codebase 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.