An introduction to running RMI over IIOP
Level: Intermediate
Damian Hagge ([email protected]), Software Engineer, IBM
12 Mar 2002
RMI and CORBA are often seen as competing technologies, since both provide transparent access to remotely distributed objects. But the two technologies are actually complementary, in that each one has strengths that can address the weaknesses of the other. The marriage of RMI and CORBA has resulted in RMI-IIOP, a cornerstone of server-side Java development for the enterprise. In this article, Java developer Damian Hagge offers a brief introduction to RMI-IIOP, then shows you how to build and run a simple, Java-based RMI-IIOP client/server application. See for yourself how well RMI works over IIOP.
IBM and Sun Microsystems launched a joint initiative in 1997 to promote the advancement of Java as an enterprise-development technology. In particular, the two companies focused on how Java could be used as a server-side language, producing enterprise-level code that could be incorporated into existing architectures. What was needed was a remote transport technology that combined the small footprint of Java's RMI (Remote Method Invocation) and the sturdiness of the more mature CORBA (Common Object Request Broker Architecture) technology. Out of this need was born RMI-IIOP, which has helped propel the Java language into its current position as the leading language for server-side enterprise development.
In this article, I'll provide a basic introduction to RMI-IIOP, with the goal of getting you started using the technology in your enterprise development solutions. In order to explain what RMI-IIOP really is I think it's important to present information about CORBA and RMI that you might not find in your typical introduction to either technology. If you're unfamiliar with the basics of either CORBA or RMI, I suggest you go through some introductory information before you proceed. See Resources for a selection of articles and tutorials.
Before I talk specifically about RMI-IIOP, we'll take a look at the mechanisms that CORBA and RMI use to marshall requests. CORBA will be our primary example, because RMI-IIOP marshalling is based on the CORBA transport protocol (IIOP). We'll go over the basic functions of the transport protocol and ORB (object request broker) in sending a request, locating a remote object, and transporting that object over a network.
Remote object transport
When a CORBA request is marshalled, it is done using the IIOP protocol. Simply put, IIOP represents the elements of any IDL (Interface Definition Language) construct in a standardized format as a series of bytes. So, assume that a Java client is dispatching a CORBA request to a C++ server. The client application has a reference to the remote object in the form of a Java interface, and it invokes an operation on that interface. Under the hood, the interface invokes its corresponding implementation of the operation, which will be in the stub (which you will have generated from IDL using idlj
).
The stub dispatches the method call down into the ORB, which consists of two parts: the client ORB and the server ORB. The client ORB's job is to marshall requests onto the network destined for a specific location. The server ORB's job is to listen for requests coming off the network and to convert them into method calls that a language implementation can understand. For a more in-depth discussion of the role of a CORBA ORB, refer to the Resources section.
After the stub dispatches the method call, the client ORB converts the request, including all parameters, into a standardized byte format, in this case IIOP. The request is then sent out on the wire to the server ORB, which should be listening for the incoming request. The server-side ORB will read in the bytes of data and convert the request to something meaningful to a C++ server implementation. The C++ server method will do its thing (that is, invoke the requested method) and return the result to the client over IIOP, using the same mechanism.
RMI handles requests similarly, but uses JRMP (Java Remote Messaging Protocol) as its transport protocol. And, of course, RMI transport involves the serialization of Java objects.
|
Remote object location
CORBA uses the CosNaming naming service to locate remote objects. CosNaming provides a framework for a name server to hold bindings (or references) to CORBA server processes. When a CORBA client sends a CosNaming request to the name service for a server process of a given name, the name service returns an interoperable object reference (IOR) for that process. The client then uses that IOR to communicate directly with the server process.
The IOR contains information about the server process, such as its location. One of the downsides of the CosNaming service is that an IOR is illegible to humans -- at least those of us who don't have cyborg brains! RMI, on the other hand, is a bit more user friendly. It locates remote objects using a registry (much like a naming service) running on top of JNDI. The RMI registry uses the Java Reference
object, which is composed of RefAddr
objects, to identify and locate remote objects. These Java objects are more user friendly than an IOR.
COBRA has fairly recently incorporated the Interoperable Naming Service (INS) into its object-location scheme. INS runs over CosNaming, using human-readable URLs as its object locations. INS doesn't make use of a naming service; instead, it sends invocations directly to the specified URL. See Resources to learn more about INS.
|
RMI versus CORBA
So, which is better: CORBA or RMI? The answer depends on what you want to do. CORBA is a big tried-and-tested architecture running over an industry-standard third- or fourth-generation protocol. When you take into account all the add-ons CORBA offers (such as transaction processing, interceptors for security, event channels, and more), CORBA seems to be the answer for enterprise applications. The big drawback with CORBA is that it's complicated. Developers generally have a steep training curve to reach CORBA fluency.
RMI, on the other hand, is fairly easy to learn. It's quite simple to create a client/server implementation, bind to a registry and remote object, and invoke and/or receive a request using RMI. RMI also has a much smaller footprint than CORBA, since JRMP is a considerably less costly protocol than IIOP. But RMI lacks CORBA's industrial-strength add-ons, and is a purely Java-based mechanism. So, what we really want is the flexibility and ease-of-use of RMI coupled with the enterprise-readiness of CORBA, right? Enter RMI-IIOP.
|
Overview of RMI-IIOP
RMI-IIOP lets you run RMI invocations over IIOP with very little modification. With RMI-IIOP you can write straightforward Java code and also use the rich suite of enterprise features that CORBA has to offer. Furthermore, the code is flexible enough to run over either RMI or IIOP. This means your code can run in a pure Java environment when a small footprint and flexibility are key or integrate into an existing CORBA infrastructure with minimal code changes.
One of the very powerful features of RMI-IIOP is that it lets you write pure-Java client/server implementations without losing the flexibility of RMI class serialization. RMI-IIOP accomplishes this by overriding Java serialization and converting the Java classes to IIOP on the wire. On the other side, the Java class is read off the wire as IIOP and a new instance of the class is created (using reflection) with all its member's values intact -- and voila: Java serialization over IIOP!
For RMI-IIOP to achieve transparent object location, ORB vendors have historically used the Java CosNaming service provider (or plug-in in layman's terms). The plug-in acts beneath the JNDI API to access the CORBA naming service. Although I do not have the space to go into the reasons here, this naming solution isn't ideal. As a result, numerous vendors -- particularly application server vendors -- have developed proprietary object location mechanisms for RMI-IIOP.
RMI-IIOP also supports INS as an extension to the Java CosNaming service. Because I believe that INS will set the direction of object location in the future, the code example we'll work with in this article uses INS.
Note: Because Sun has not yet fully complied with the OMG INS standard and has not exposed register_initial_reference
on the org.omg.CORBA.ORB
interface, the source code presented in this article will not work with the Sun JDK. You will need to use the IBM Developer Kit for Java technology, version 1.3.1 or greater. I have, however, created a Sun-compatible example that uses a naming service, which you can download from the Resources section.
|
Do-it-yourself RMI-IIOP
Enough talk, let's code! In the following sections we'll build a simple, Java-based client/server RMI-IIOP application. This application consists of three parts: the RMI interface, the server application, and the client application. The example features Java serialization over IIOP, so you can see how a Java class can be instantiated by the client, passed to the server, altered by the server, and passed back to the client with the alterations intact.
|
Part 1: Define the interface
Under RMI-IIOP we can choose to define our interface using RMI or IDL. Because we want to see how RMI runs over IIOP, we'll define the example interface using RMI. Listing 1 is the RMI interface for our simple example:
/* |
The RMIInterface
defines a hello()
method and an alterClass(SerClass)
method. This method takes SerClass
, a Java class that implements Serializable
, and returns a class of the same type. SerClass
is a simple class with a few members and corresponding getter methods. The methods are shown in Listing 2:
/** |
That's all there is to our simple interface. Now let's check out the server class.
|
Part 2: Build the server
We'll use a server class (Server.java
) that both acts as the RMIInterface
implementation class and contains the main method to start our service. Server.java
extends javax.rmi.PortableRemoteObject
. In this way, it contains all the functionality it needs to bind itself as a Remote
interface to the ORB and begin listening for requests. Listing 3 is the code for the server:
/* |
Er, what's happening here?
The server application is code heavy, so let's break it down. First of all, as previously mentioned, the Server
class implements RMIInterface
and provides implementations for all of its methods. You can see the implementations of the RMIInterface
's hello()
and alterClass(SerClass)
methods at the beginning of the code. The hello()
method simply returns the String "Hello there!" The alterClass(SerClass)
method takes a SerClass
object, alters the member values, and returns the new object -- all using RMI-IIOP.
Server.java
's main method initializes an ORB. It passes in the com.ibm.CORBA.ListenerPort
property set to 8080 as a parameter. This will cause the ORB to listen for incoming requests on port 8080. Note that com.ibm.CORBA.ListenerPort
is a proprietary IBM property. If you want to run this code on another vendor's ORB you should refer to that vendor's documentation for the appropriate property. (Sun uses com.sun.CORBA.POA.ORBPersistentServerPort
, but it only works if you're using POA (portable object adapter) servants.)
With the ORB initialized, the main method moves on to instantiate a Server
object. Because the server object is also a PortableRemoteObject
, the default constructor automatically makes a call to exportObject(this)
. The object is now ready to receive remote calls.
Next, we need to register the object with a call to ORB.register_initial_reference(String,orb.omg.CORBA.Object)
. For this, we need to have a reference to our server object as an org.omg.CORBA.Object
. The call PortableRemoteObject.toStub(s)
accomplishes this, because the returned object implements both java.rmi.Remote
and org.omg.CORBA.Object
.
The returned org.omg.CORBA.Object
object is then registered with the server-side ORB as "OurLittleClient". To ensure that an INS request can locate the object, we use the registration call register_initial_reference
. When an INS call comes into an ORB, the ORB will look for an object that has been registered under the name being requested. Since we registered the object as "OurLittleClient", we'll know what object the client is looking for when an INS call comes into our server ORB for "OurLittleClient".
Finally, I'm sure that you've noticed that we cast the ORB to a com.ibm.CORBA.iiop.ORB
. Because Sun has not yet exposed register_initial_reference
on the org.omg.CORBA.ORB
interface, the IBM SDK cannot expose it either. Therefore we must cast our ORB into an IBM ORB. Future versions (post 1.4.0) of the JDK will probably not require this cast, as Sun's compliance with the OMG evolves.
And that's it! Easy-peasy -- well, sort of. Our server is now awaiting incoming client INS requests. But what about that client?
|
Part 3: Build the client
The code for the client application is shown in Listing 4:
/* |
How the client code breaks down
The client code is somewhat simpler than the server code. We initialize an ORB and make a call to string_to_object(String)
, where the string is our INS URL. Constructing the INS URL is relatively simple: first we specify that we're using a corbaloc URL (see Resources) and the IIOP protocol version 1.2. Next we add in the host name (www.whatever.com) and the port on which to connect. And, finally, we specify the name of the service we're looking for. The resulting INS URL is corbaloc:iiop:1.2@localhost:8080/OurLittleClient.
When we pass this URL into ORB.string_to_object(String)
the ORB will dispatch a request to the specified server for the requested service. Assuming that everything is working as it should, the ORB will receive back an object reference (really an IOR) for the service. We then narrow the object reference into something we can use, namely an RMIInterface
, and we're ready to start calling methods.
After we've called the simple hello method (which should require no explanation), we can begin exploring RMI-IIOP's serialization feature. First, we create a SerClass
, a serializable Java class, and initialize its member variables. Next, we pass this class into our method, which writes it out over IIOP to the server. The server reads the class in and re-creates it as a server-side Java object, alters its member values, and returns it (using IIOP) as the return value of our method. When we receive the re-created object after the remote method call, we see that its members have indeed been changed by the server. And it's that simple: Java serialization over IIOP.
|
Part 4: Run the example
Note that the example we've created here must be run with an IBM Developer Kit for Java technology, version 1.3.1 or greater. If you prefer to use a Sun JDK please download the Sun-specific source code, which should be run with a Sun 1.4.0 JDK or greater. This source code includes a readme.txt file that explains the differences between the IBM SDK and the Sun JDK versions. If you don't have an IBM Developer Kit for Java technology (and you want one) download one now; they're free.
Here's how to run the example:
javac *.java
.rmic
on the server class with the IIOP flag: rmic -iiop Server
.start java Server
.start java Client
.
|
A note about RMI-IIOP and EJB components
The EJB 2.0 specification states that EJB components must be able to run over both RMI and RMI-IIOP. The addition of RMI-IIOP as an on-the-wire protocol for EJB components has greatly assisted the integration of the J2EE environment into existing corporate infrastructures, most of which are quite CORBA intensive. But it also poses some problems.
The short version is that integrating custom-built components and EJB components requires you (the developer) to deal with the plumbing, which would otherwise be abstracted for you by the EJB architecture. As of yet, there is no simple solution to this problem, and there may never be. The solution could come from evolving technologies such as Web services, but that is as yet unknown.
|
Conclusion: Where to go from here
It is my hope that this article has shown you how easy it is to build and run RMI-IIOP client/server applications. You can play around with the example we used by replacing the client or server with pure CORBA, though doing so would cut Java serialization out of your application.
If you're thinking of using RMI-IIOP in a CORBA environment it would be worthwhile to see how IDL maps into Java and vice versa. If you're thinking of deploying RMI-IIOP in an insecure environment (that is, not your own PC) it would be a good idea to look into CORBA security features such as interceptors and the CORBA security model, as well as other CORBA enterprise features such as transaction processing. All of CORBA's rich features are available to you when you run RMI-IIOP.
Resources
About the author
Damian Hagge works at IBM's Hursley Development Laboratories. Although he currently lives in the UK, he originated five hours counterclockwise from his present location just above the forty-ninth parallel (that's Canada for those whose geography isn't up to scratch). Aside from working as a developer on the IBM Java ORB team and writing long articles on obscure subject matter, Damian is a veritable encyclopedia of useless facts; for instance, did you know that Iceland supports a self-sustaining banana-growing industry? Damian also does some normal stuff like power lifting (if you call that normal!). You can contact him at [email protected]. |