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 connectivity to many other programming languages and platforms. RMI-IIOP enables distributed Web-enabled Java remote 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.
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:
There are three tasks to complete in this section:
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()
	
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:
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.
	java.rmi.Remote
	interface.
	java.rmi.RemoteException
	(or a superclass of RemoteException) in its
	throws clause, in addition to any
	application-specific exceptions.
	
	HelloInterface) not the
	implementation class (HelloImpl).
For this example, create all of the source files in the same directory, for example,
$HOME/mysrc/RMIHelloPOA.  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.
At a minimum, a remote object implementation class, HelloImpl.java
 must:
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!!" );
  }
}
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.  
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:
java.rmi.RemoteException
HelloImpl
class:
  public HelloImpl() throws java.rmi.RemoteException { 
    super(); 
  }
Note the following:
super method call invokes the no-argument
	constructor of
	javax.rmi.PortableRemoteObject, which exports
	the remote object.
	
	java.rmi.RemoteException, because RMI's attempt to
	export a remote object during construction might fail if
	communication resources are not available.
java.rmi.RemoteException
is a checked exception, not a runtime exception.
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.
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:
static or transient.
	
	
	rmic to generate stubs and skeletons.
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:
HelloServer.java:
//HelloServer.java
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.rmi.PortableRemoteObject ;
//Please note that internal Sun APIs
//may change in future releases.
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
      //Please note that the name of the servertool 
      //class may change in future releases.
      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 );
  }
}
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 was introduced 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:
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.
	 
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. 
	  
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://www.omg.org/cgi-bin/doc?formal/99-10-07
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();
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 );
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:
"HelloService", is a 
	java.lang.String, representing the name of the remote object to bind
	
	
tPOA.create_reference_with_id(id, 
          tie._all_interfaces(tPOA,id)[0] is the object id of the remote object
           to bind
      orb.run();
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.  
	
_HelloImpl_Stub instance bound to
			that name
			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)
	
sayHello() method on
	the server's remote object, causing the
	string "It works! Hello World!!" to be displayed on the command line.
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
	
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:
rmic to generate stubs and
	skeletons
	
	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.
rmic to generate skeletons and stubsrmic
 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
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.
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.
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 ...
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
in your terminal window or command prompt window:
Client: Obtained a ref. to Hello server.
The server window will return the following message:
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:
| Copyright © 2001-2004 Sun Microsystems, Inc. All Rights Reserved. |   |