Getting Started Using RMI-IIOP: POA Model

# Getting Started Using RMI-IIOP:
Example Using POA-based server-side model

This tutorial shows you the steps to follow to create a distributed version of the classic "Hello World" program using JavaTM Remote Method Invocation (RMI) over Internet Inter-ORB Protocol (IIOP). RMI-IIOP adds CORBA (Common Object Request Broker Architecture) capability to Java RMI, providing standards-based interoperability and connectivity to many other programming languages and platforms. RMI-IIOP enables distributed Web-enabled Java applications to transparently invoke operations on remote network services using the industry standard IIOP defined by the Object Management Group. Runtime components include a Java ORB for distributed computing using IIOP communication.

RMI-IIOP is for Java programmers who want to program to the RMI interfaces, but use IIOP as the underlying transport. RMI-IIOP provides interoperability with other CORBA objects implemented in various languages - but only if all the remote interfaces are originally defined as Java RMI interfaces. It is of particular interest to programmers using Enterprise JavaBeans (EJB), since the remote object model for EJBs is RMI-based.

Another option for creating distributed applications is JavaTM IDL. Java IDL is for CORBA programmers who want to program in the Java programming language based on interfaces defined in CORBA Interface Definition Language (IDL). This is "business as usual" CORBA programming, supporting Java in exactly the same way as other languages like C++ or COBOL.

# Tutorial: The Hello World Application

The distributed Hello World example uses a client application to make a remote method call via IIOP to a server, running on the host from which the client was downloaded. When the client runs, "Hello World!" is displayed.

This tutorial is organized as follows:

1. The steps to write the source files
2. The steps to compile the class files
3. The steps to start the Naming Service, server, and client

# Write the Source Files

There are three tasks to complete in this section:

1. Define the functions of the remote class as an interface written in the Java programming language
2. Write the implementation class
3. Write the server class
4. Write a client program that uses the remote service

The source files created in this tutorial are:

- `HelloInterface.java` - a remote interface
- `HelloImpl.java` - a remote object implementation that implements `HelloInterface`
- `HelloServer.java` - an RMI server that creates an instance of the remote object implementation and binds that instance to a name in the Naming Service
- `HelloClient.java` - a client application that invokes the remote method, `sayHello()`

## Define the functions of the remote class as an interface written in the Java programming language

In the Java programming language, a remote object is an instance of a class that implements a `Remote` interface. Your remote interface will declare each of the methods that you would like to call from other machines. Remote interfaces have the following characteristics:

- The remote interface must be declared `public`. Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface, unless that client is in the same package as the remote interface.
- The remote interface extends the `java.rmi.Remote` interface.
- Each method must declare `java.rmi.RemoteException` (or a superclass of `RemoteException`) in its `throws` clause, in addition to any application-specific exceptions.
- The data type of any remote object that is passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface type (for example, `HelloInterface`) not the implementation class (`HelloImpl`).

For this example, create all of the source files in the same directory, for example, `$HOME/mysrc/helloWorld`. Here is the interface definition for the remote interface, `HelloInterface`. The interface contains just one method, `sayHello`:

    //HelloInterface.java
    import java.rmi.Remote;

    public interface HelloInterface extends java.rmi.Remote {
      public void sayHello() throws java.rmi.RemoteException;
    }

Because remote method invocations can fail in very different ways from local method invocations (due to network-related communication problems and server problems), remote methods will report communication failures by throwing a `java.rmi.RemoteException`. If you want more information on failure and recovery in distributed systems, you may wish to read A Note on Distributed Computing.

## Write The Implementation Class

At a minimum, a remote object implementation class, `HelloImpl.java` must:

- Declare that it implements at least one remote interface
- Define the constructor for the remote object
- Provide implementations for the methods that can be invoked remotely

An explanation of each of the preceding steps follows the source for `HelloImpl.java:`

    //HelloImpl.java
    import javax.rmi.PortableRemoteObject;

    public class HelloImpl extends PortableRemoteObject implements HelloInterface {
      public HelloImpl() throws java.rmi.RemoteException {
        super();     // invoke rmi linking and remote object initialization
      }

      public void sayHello() throws java.rmi.RemoteException {
        System.out.println( "It works!  Hello World!!" );
      }
    }

### Implement a remote interface

In the Java programming language, when a class declares that it implements an interface, a contract is formed between the class and the compiler. By entering into this contract, the class is promising that it will provide method bodies, or definitions, for each of the method signatures declared in that interface. Interface methods are implicitly `public` and `abstract`, so if the implementation class doesn't fulfill its contract, it becomes by definition an `abstract` class, and the compiler will point out this fact if the class was not declared `abstract`.

The implementation class in this example is `HelloImpl`. The implementation class declares which remote interface(s) it is implementing. Here is the `HelloImpl` class declaration:

      public class HelloImpl extends PortableRemoteObject
        implements HelloInterface{

As a convenience, the implementation class can extend a remote class, which in this example is `javax.rmi.PortableRemoteObject`. By extending `PortableRemoteObject`, the `HelloImpl` class can be used to create a remote object that uses IIOP-based transport for communication.

### Define the constructor for the remote object

The constructor for a remote class provides the same functionality as the constructor for a non-remote class: it initializes the variables of each newly created instance of the class, and returns an instance of the class to the program which called the constructor.

In addition, the remote object instance will need to be "exported". Exporting a remote object makes it available to accept incoming remote method requests, by listening for incoming calls to the remote object on an anonymous port. When you extend `javax.rmi.PortableRemoteObject`, your class will be exported automatically upon creation.

Because the object export could potentially throw a `java.rmi.RemoteException`, you must define a constructor that throws a `RemoteException`, even if the constructor does nothing else. If you forget the constructor, `javac` will produce the following error message:

    	HelloImpl.java:3: unreported exception java.rmi.RemoteException; must be
    	caught or declared to be thrown.

            public class HelloImpl extends PortableRemoteObject implements HelloInterface{
                   ^
            1 error

To review: The implementation class for a remote object needs to:

- Implement a remote interface
- Export the object so that it can accept incoming remote method calls
- Declare its constructor(s) to throw at least a `java.rmi.RemoteException`

Here is the constructor for the `HelloImpl` class:

      public HelloImpl() throws java.rmi.RemoteException {
        super();
      }

Note the following:

- The `super` method call invokes the no-argument constructor of `javax.rmi.PortableRemoteObject`, which exports the remote object.
- The constructor must throw `java.rmi.RemoteException`, because RMI's attempt to export a remote object during construction might fail if communication resources are not available.

If you are interested in why `java.rmi.RemoteException` is a checked exception rather than runtime exception, please refer to the archives of the rmi-users email list:

`http://java.sun.com/products/jdk/rmi/archives/3490.html`

Although the call to the superclass's no-argument constructor, `super()`, occurs by default (even if omitted), it is included in this example to make clear the fact that the superclass will be constructed before the class.

### Provide an implementation for each remote method

The implementation class for a remote object contains the code that implements each of the remote methods specified in the remote interface. For example, here is the implementation for the `sayHello()` method, which returns the string "It works! Hello World!!" to the caller:

      public void sayHello() throws java.rmi.RemoteException {
        System.out.println( "It works!  Hello World!!" );
      }

Arguments to, or return values from, remote methods can be any data type for the Java platform, including objects, as long as those objects implement the interface `java.io.Serializable`. Most of the core classes in `java.lang` and `java.util` implement the `Serializable` interface. In RMI:

- By default, local objects are passed by copy, which means that all data members (or fields) of an object are copied, except those marked as `static` or `transient`. Please refer to the Java Object Serialization Specification for information on how to alter the default serialization behavior.
- Remote objects are passed by reference. A reference to a remote object is actually a reference to a stub, which is a client-side proxy for the remote object. Stubs are described fully in the Java Remote Method Invocation Specification. We'll create them later in this tutorial in the section: Use `rmic` to generate stubs and skeletons.

## Write The Server Class

A server class is the class which has a `main` method that creates an instance of the remote object implementation, and binds that instance to a name in the Naming Service. The class that contains this `main` method could be the implementation class itself, or another class entirely.

In this example, the `main` method is part of `HelloServer.java`, which does the following:

- Creates a Portable Object Adapter (POA) with the appropriate policies
- Activates the POA Managers
- Creates an instance of the servant and activates the Tie, creates an instance of a remote object
- Publishes the object reference
- Prepares to accept requests from the client

An explanation of each of the preceding steps follows the source for `HelloServer.java:`

    //HelloServer.java
    import javax.naming.InitialContext;
    import javax.naming.Context;
    import javax.rmi.PortableRemoteObject ;
    import com.sun.corba.se.internal.POA.POAORB;
    import org.omg.PortableServer.*;
    import java.util.*;
    import org.omg.CORBA.*;
    import javax.rmi.CORBA.Stub;
    import javax.rmi.CORBA.Util;

    public class HelloServer {
      public HelloServer(String[] args) {
        try {
          Properties p = System.getProperties();
          // add runtime properties here
          p.put("org.omg.CORBA.ORBClass",
              "com.sun.corba.se.internal.POA.POAORB");
          p.put("org.omg.CORBA.ORBSingletonClass",
              "com.sun.corba.se.internal.corba.ORBSingleton");

          ORB orb = ORB.init( args, p );

          POA rootPOA = (POA)orb.resolve_initial_references("RootPOA");

          // STEP 1: Create a POA with the appropriate policies
          Policy[] tpolicy = new Policy[3];
          tpolicy[0] = rootPOA.create_lifespan_policy(
            LifespanPolicyValue.TRANSIENT );
          tpolicy[1] = rootPOA.create_request_processing_policy(
            RequestProcessingPolicyValue.USE_ACTIVE_OBJECT_MAP_ONLY );
          tpolicy[2] = rootPOA.create_servant_retention_policy(
            ServantRetentionPolicyValue.RETAIN);
          POA tPOA = rootPOA.create_POA("MyTransientPOA", null, tpolicy);

          // STEP 2: Activate the POA Manager, otherwise all calls to the
          // servant hang because, by default, POAManager will be in the
          // HOLD state.
          tPOA.the_POAManager().activate();

          // STEP 3: Instantiate the Servant and activate the Tie, If the
          // POA policy is USE_ACTIVE_OBJECT_MAP_ONLY
          HelloImpl helloImpl = new HelloImpl();
          _HelloImpl_Tie tie = (_HelloImpl_Tie)Util.getTie( helloImpl );
          String helloId = "hello";
          byte[] id = helloId.getBytes();
          tPOA.activate_object_with_id( id, tie );

          // STEP 4: Publish the object reference using the same object id
          // used to activate the Tie object.
          Context initialNamingContext = new InitialContext();
          initialNamingContext.rebind("HelloService",
            tPOA.create_reference_with_id(id,
              tie._all_interfaces(tPOA,id)[0]) );
          System.out.println("Hello Server: Ready...");

          // STEP 5: Get ready to accept requests from the client
          orb.run();
        }
          catch (Exception e) {
            System.out.println("Problem running HelloServer: " + e);
            e.printStackTrace();
          }
      }

      public static void main(String args[]) {
        new HelloServer( args );
      }
    }

### Create a Portable Object Adapter (POA) with the appropriate policies

The `main` method of the server first needs to create a Portable Object Adapter (POA) with the appropriate policies. For example:

          Policy[] tpolicy = new Policy[3];
          tpolicy[0] = rootPOA.create_lifespan_policy(
            LifespanPolicyValue.TRANSIENT );
          tpolicy[1] = rootPOA.create_request_processing_policy(
            RequestProcessingPolicyValue.USE_ACTIVE_OBJECT_MAP_ONLY );
          tpolicy[2] = rootPOA.create_servant_retention_policy(
            ServantRetentionPolicyValue.RETAIN);
          POA tPOA = rootPOA.create_POA("MyTransientPOA", null, tpolicy);

The Portable Object Adaptor (POA) is designed to provide an object adapter that can be used with multiple ORB implementations with a minimum of rewriting needed to deal with different vendors' implementations. POA support is new in J2SE version 1.4.

The POA is also intended to allow persistent objects -- at least, from the client's perspective. That is, as far as the client is concerned, these objects are always alive, and maintain data values stored in them, even though physically, the server may have been restarted many times, or the implementation may be provided by many different object implementations.

The POA allows the object implementor a lot more control. Previously, the implementation of the object was responsible only for the code that is executed in response to method requests. Now, additionally, the implementor has more control over the object's identity, state, storage, and lifecycle.

In this example, the policy values include:

- The `LifespanPolicyValue` can have the following values:
  - `TRANSIENT` - The objects implemented in the POA cannot outlive the POA instance in which they are first created.
  - `PERSISTENT` - The objects implemented in the POA can outlive the process in which they are first created.

- The `RequestProcessingPolicyValue` can have the following values:
  - `USE_ACTIVE_OBJECT_MAP_ONLY` - If the object ID is not found in the Active Object Map, an `OBJECT_NOT_EXIST` exception is returned to the client. The `RETAIN` policy is also required.
  - `USE_DEFAULT_SERVANT` - If the object ID is not found in the Active Object Map or the `NON_RETAIN` policy is present, and a default servant has been registered with the POA using the `set_servant` operation, the request is dispatched to the default servant.
  - `USE_SERVANT_MANAGER` - If the object ID is not found in the Active Object Map or the `NON_RETAIN` policy is present, and a servant manager has been registered with the POA using the `set_servant_manager` operation, the servant manager is given the opportunity to locate a servant or raise an exception.

- The `ServantRetentionPolicyValue` can have the following values.
  - `RETAIN` - to indicate that the POA will retain active servants in its Active Object Map. If no `ServantRetentionPolicy` is specified at POA creation, the default is `RETAIN`.
  - `NON_RETAIN` - to indicate Servants are not retained by the POA.

For more information on POA policies, refer to Chapter 11, Portable Object Adapter of the CORBA/IIOP 2.3.1 Specification at http://cgi.omg.org/cgi-bin/doc?formal/99-10-07

### Activate the POA Managers

Each POA object has an associated `POAManager` object. A POA Manager may be associated with one or more POA objects. A POA Manager encapsulates the processing state of the POAs it is associated with. In this step, the POA Manager is activated. If this step is missing, all calls to the `Servant` would hang because, by default, the POA Manager will be in the `HOLD` state.

          tPOA.the_POAManager().activate();

### Create an instance of a remote object and activate the Tie

The `main` method of the server needs to create an instance of the remote object implementation, or Servant. For example:

        HelloImpl helloImpl = new HelloImpl();

The constructor exports the remote object, which means that once created, the remote object is ready to accept incoming calls.

When using RMI-IIOP technology, your implementations use delegation (known as the Tie model) to associate your implementation with the interface. When you create an instance of your implementation, as above, you also need to create a Tie object to associate it with a CORBA interface. The next few lines of code activate the Tie, but only if the POA policy is `USE_ACTIVE_OBJECT_MAP_ONLY`.

          _HelloImpl_Tie tie = (_HelloImpl_Tie)Util.getTie( helloImpl );
          String helloId = "hello";
          byte[] id = helloId.getBytes();
          tPOA.activate_object_with_id( id, tie );

### Publish the object reference using the same object id used to activate the Tie object

For a caller (client, peer, or client application) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.

Once a remote object is registered on the server, callers can look up the object by name (using a naming service), obtain a remote object reference, and then remotely invoke methods on the object. In this example, we use the Object Request Broker Daemon (`orbd`), which is a daemon process containing a Bootstrap Service, a Transient Naming Service, a Persistent Naming Service, and a Server Manager.

For example, the following code binds the name "HelloService" to a reference for the remote object:

          Context initialNamingContext = new InitialContext();
          initialNamingContext.rebind("HelloService",
            tPOA.create_reference_with_id(id,
              tie._all_interfaces(tPOA,id)[0]) );
          System.out.println("Hello Server: Ready...");

Note the following about the arguments to the `rebind` method call:

- The first argument, `"HelloService"`, is a `java.lang.String`, representing the name of the remote object to bind

- The second argument, `tPOA.create_reference_with_id(id, tie._all_interfaces(tPOA,id)[0]` is the object id of the remote object to bind

### Prepare to accept requests from the client

The following line of code, when called by the main thread, enables the ORB to perform work using the main thread.

          orb.run();

## Write a client program that uses the remote service

The client application in this example remotely invokes the `sayHello` method in order to get the string "Hello World!" to display when the client application runs. Here is the code for the client application:

    //HelloClient.java
    import java.rmi.RemoteException;
    import java.net.MalformedURLException;
    import java.rmi.NotBoundException;
    import javax.rmi.*;
    import java.util.Vector;
    import javax.naming.NamingException;
    import javax.naming.InitialContext;
    import javax.naming.Context;

    public class HelloClient {

      public static void  main( String args[] ) {
        Context ic;
        Object objref;
        HelloInterface hi;

        try {
          ic = new InitialContext();
        } catch (NamingException e) {
            System.out.println("failed to obtain context" + e);
            e.printStackTrace();
            return;
        }

        // STEP 1: Get the Object reference from the Name Service
        // using JNDI call.
        try {
          objref = ic.lookup("HelloService");
          System.out.println("Client: Obtained a ref. to Hello server.");
        } catch (NamingException e) {
            System.out.println("failed to lookup object reference");
            e.printStackTrace();
            return;
        }

        // STEP 2: Narrow the object reference to the concrete type and
        // invoke the method.
        try {
          hi = (HelloInterface) PortableRemoteObject.narrow(
            objref, HelloInterface.class);
          hi.sayHello();
        } catch (ClassCastException e) {
            System.out.println("narrow failed");
            e.printStackTrace();
            return;
          } catch( Exception e ) {
                System.err.println( "Exception " + e + "Caught" );
                e.printStackTrace( );
                return;
            }
      }
    }

First, the client application gets a reference to the remote object implementation (advertised as "HelloService") from the Name Service using Java Naming and Directory Interface [TM] (JNDI) calls. Like the `Naming.rebind` method, the `Naming.lookup` method takes `java.lang.String` value representing the name of the object to look up. You supply `Naming.lookup()` the name of the object you want to look up, and it returns the object bound to that name.

- `Naming.lookup()` takes care of these tasks:
  - The Name Service returns the `_HelloImpl_Stub` instance bound to that name
  - The `lookup` method receives the remote object's (`HelloImpl`) stub instance and loads the stub class (`_HelloImpl_Stub`)
  - `Naming.lookup` returns the stub to its caller (`HelloClient`)

- The client application invokes the remote `sayHello()` method on the server's remote object, causing the string "It works! Hello World!!" to be displayed on the command line.

# Compile Class Files

The source code for this example is now complete and the directory contains four files:

- `HelloInterface.java` contains the source code for the remote interface
- `HelloImpl.java` contains the source code for the remote object implementation
- `HelloServer.java` contains the source code for the server
- `HelloClient.java` contains the source code for the client application

In this section, you compile the remote object implementation file, `HelloImpl.java`, in order to create the `.class` files needed to run `rmic`. You then run the `rmic` compiler to create stubs and skeletons. A stub is a client-side proxy for a remote object which forwards RMI-IIOP calls to the server-side dispatcher, which in turn forwards the call to the actual remote object implementation. The last task is to compile the remaining `.java` source files to create `.class` files.

The following tasks will be completed in this section:

1. Compile the remote object implementation
2. Use `rmic` to generate stubs and skeletons
3. Compile the source files

## Compile the remote object implementation

To create stub and skeleton files, the `rmic` compiler must be run on the fully-qualified package names of compiled class files that contain remote object implementations. In this example, the file that contains the remote object implementations is `HelloImpl.java`. In order to generate the stubs and skeletons, we must first compile `HelloImpl.java`, as follows:

        javac -d . -classpath . HelloImpl.java

The "`-d .`" option indicates that the generated files should be placed in the directory from which you are running the compiler. The "`-classpath .`" option indicates that files on which `HelloImpl.java` is dependent can be found in this directory.

## Use `rmic` to generate skeletons and stubs

To create CORBA-compatible stub and skeleton files, run the `rmic` compiler with the `-poa -iiop` option. The `rmic -poa -iiop` command takes one or more class names as an argument and produces class files of the form `_MyImpl_Tie.class` and `_MyInterface_Stub.class`. The remote implementation file, `HelloImpl.class`, is the class name to pass in this example.

For an explanation of `rmic` options, refer to the Solaris Operating Environment `rmic` manual page or the Microsoft Windows `rmic` manual page.

To create the stub and skeleton for the `HelloImpl` remote object implementation, run `rmic` like this:

        rmic -poa -iiop HelloImpl

The preceding command creates the following files:

- `_HelloInterface_Stub.class` - the client stub
- `_HelloImpl_Tie.class` - the server skeleton

## Compile the source files

To compile the source files, run the `javac` command as follows:

        javac -d . -classpath . HelloInterface.java HelloServer.java HelloClient.java

This command creates the class files `HelloInterface.class`, `HelloServer.class`, and `HelloClient.class`. These are the remote interface, the server, and the client application respectively. For an explanation of `javac` options, you can refer to the Solaris `javac` manual page or the Microsoft Windows `javac` manual page.

# Start the Naming Service, server, and client application

The following tasks will be completed in this section:

1. Start the Naming Service
2. Start the server
3. Run the client application

## Start the Naming Service

For this example, we will use the Object Request Broker Daemon, `orbd`, which includes both a Transient and a Persistent Naming Service, and is available with every download of J2SE 1.4 and higher.

For a caller (client, peer, or client application) to be able to invoke a method on a remote object, that caller must first obtain a reference to the remote object.

Once a remote object is registered on the server, callers can look up the object by name, obtain a remote object reference, and then remotely invoke methods on the object.

To start the Naming Service, run `orbd` from the command line. This command produces no output and is typically run in the background. For more on the `orbd` tool, you can refer to the `orbd` manual page.

For this example, on the Solaris operating system:

        orbd -ORBInitialPort 1060&

or, on the Microsoft Windows operating system:

        start orbd -ORBInitialPort 1060

You must specify a port on which to run `orbd`. For this example the port of `1060` is chosen because in the Solaris operating environment, a user must become root to start a process on a port under 1024.

You must stop and restart the server any time you modify a remote interface or use modified/additional remote interfaces in a remote object implementation. Otherwise, the type of the object reference bound in the Naming Service will not match the modified class.

## Start the server

Open another terminal window and change to the directory containing the source files for this example. The command for running the client has been spread out below to make it easier to read, but should be typed without returns between the lines. The following command shows how to start the `HelloServer` server. Of course, if you used a port other than 1060 or a host other than localhost when starting the `orbd` tool, replace those values in the command below with the actual values used to start `orbd`.

        java
          -classpath .
          -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
          -Djava.naming.provider.url=iiop://localhost:1060
          HelloServer &

For an explanation of `java` options, you can refer to the Solaris `java` manual page or the Microsoft Windows `java` manual page.

The output should look like this:

    Hello Server: Ready ...

## Run the client application

Once the Naming Service and server are running, the client application can be run. From a new terminal window, go to the source code directory, and run the client application from the command line, as shown below. The command for running the client has been spread out below to make it easier to read, but should be typed without returns between the lines. Of course, if you used a port other than 1060 or a host other than localhost when starting the `orbd` tool, replace those values in the command below with the actual values used to start `orbd`.

        java
          -classpath .
          -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
          -Djava.naming.provider.url=iiop://localhost:1060
          HelloClient &

After running the client application, you will see output similar to the following on your display:

    Client: Obtained a ref. to Hello server.
    It works! Hello World!!

This completes the tutorial. If you are ready to move on to more complicated applications, here are some sources that may help:

- http://forum.java.sun.com/forum.jsp?forum=59, The Sun Developer's Forum for RMI-IIOP. You must register with the Java Developer Connection to access this site.

- EJB clients interact with the J2EE EJB tier using the RMI-IIOP protocol. For more information on using RMI-IIOP in this way, see the Java 2 Platform, Enterprise Edition Blueprints or the EJB Tutorials.

- The Java IDL tutorial includes an example for running the client and server on different machines. The concepts of Running the Hello World Example on Two Machines apply to this example as well.

Copyright © 2001 Sun Microsystems, Inc. All Rights Reserved. Please send questions or comments to: rmi-iiop@sun.com |