| ## Java 1.1 Unleashed # - 54 - Remote Objects and the Java IDL System by Mike Fletcher IN THIS CHAPTER Using Objects in a Networked Environment Using the java.rmi Package Using the IDL Compiler One of Java's most useful new features is its ability to invoke methods on objects running on a remote virtual machine. This Remote Method Invocation (RMI) facility, along with the CORBA (Common Object Request Broker Architecture) IDL (Interface Definition Language) compiler libraries, make Java a very attractive platform for client/server applications. This chapter covers both the Java native remote method system and the CORBA interface. An overview of the two different systems is given first, followed by examples using both. ## Using Objects in a Networked Environment From the start, Java has had network capabilities in the form of the classes in the `java.net` package. The functionality provided by these classes is very low level (raw network streams, or packets). The introduction of the Remote Method Invocation classes and the CORBA interface has raised the level of abstraction. These two packages provide a way of accessing data and methods in the form of objects over a network. The development and widespread deployment of networks has changed how computers are used. The trend in network computing over the past few years has been towards client/server systems. In client/server systems, a local application (the client) provides the interface to a remote database or application (the server). The Web itself is an example of a client/server system: the Web browsers are the clients and the Web sites are the servers. Client/server systems rely on network services to communicate information between themselves, but for the most part, that is all they send: information. Distributed object systems combine client/server systems with another trend in computing: object-oriented programming. Rather than sending just information, distributed object systems transmit both data and code to manipulate that data. Another benefit of distributed objects is the ability for an object on one host to invoke a method on an object located on a remote host across the network. Imagine that you were writing a tic-tac-toe game to be played over the network. In a conventional client/server approach, you would need to worry about things such as creating network connections between players and developing a protocol for sending moves back and forth. This is not to say that the game could not be developed, simply that there is a lot more work to be done to do so. With a distributed object system, many of these lower level details are hidden from the programmer. For example, the server object would have a method that registers a player's move. A player's client would simply obtain a reference to the server object and call the appropriate method on this object (just as if the server object resided on the same machine). A system for distributed objects has to take many things into consideration: How are remote objects referenced? What representation is used for transmitting an object or parameters for a method call (a process known as marshaling in distributed object circles)? What character set is used for strings? Are integers represented as little-endian (the low order byte of a word comes first, as with Motorola processors) or big-endian (the high order byte of a word comes first, as with Intel processors)? What happens if there is a network problem during a method call? Sun Microsystems is no stranger to solving such problems. Its Remote Procedure Call (RPC) and External Data Representation (XDR) protocols have been in wide use on UNIX platforms for many years. The Network File System (NFS), used to share file systems between machines, and the Network Information System (NIS, formerly known as YP), used to provide a distributed database of configuration information (such as user accounts or host name to IP address databases), are both implemented using RPC. ### Why Two Different Solutions? You may be asking yourself why Sun is providing two different solutions to solve the same problem. The answer is that each of the remote object systems has its own particular advantages. The RMI system provides Java-native access to remote objects. Because it is written specifically for Java in Java, the RMI system allows transparent access to remote objects. Once a reference is obtained for a remote object, it is treated just like any other Java object. The code accessing the remote object may not even be aware that the object does not reside on the same host. The downside to this approach is that the RMI system may only be used to interface to servers written in Java. The IDL interface provides access to clients and servers using the industry standard CORBA protocol specifications. An application that uses the IDL compiler can connect to any object server that complies with the CORBA standards and uses a compatible transport mechanism. Unlike the RMI system, CORBA is intended to be a language-neutral system. Objects must be specified using the OMG Interface Definition Language, and access must be through library routines that translate the calls into the appropriate CORBA messages. ### Remote Method Invocation System The RMI system uses Java interfaces and a special "stub" compiler to provide transparent access to remote objects. An interface is defined by specifying the methods provided by the remote object. Next, a server class is defined to implement the interface. The stub compiler is invoked to generate classes that act as the glue between the local representation of an object and the remote object residing on the server. The RMI system also provides a naming service that allows servers to bind object references to URLs such as `rmi://foohost.com/ObjectName`. A client passes a URL to the `Naming` class's `lookup()` method, which returns a reference to an object implementing the appropriate interface. ### Interface Definition Language and CORBA CORBA is a part of the Object Management Group's (OMG) Object Management Architecture. The OMG is an industry consortium formed in 1989 to help provide standards for object-oriented technology. The architecture consists of four standards: Common Object Request Broker Architecture (CORBA). This standard specifies the interactions between a client object and an Object Request Broker (ORB). The ORB receives an invocation request, determines which object can handle the request, and passes the request and parameters to the servicing object. Common Object Services Specification (COSS) COSS provides a standard interface for operations such as creating and relocating objects. Common Facilities This standard specifies common application functionalities such as printing, e-mail, and document management. Applications Objects These standard objects provide for common business functions. NOTE: For a more complete introduction to CORBA and related standards, check out the OMG's home page at `http://www.omg.org/`. Another useful URL is `http://www.omg.org/ed.htm`, which has a pointer to a list of books on distributed objects (and CORBA in particular). The Java IDL system provides a mapping from the CORBA object model into Java classes. The IDL compiler provides stub classes. These stubs call an ORB core that handles details such as determining the transport mechanism to use (such as Sun's NEO or the OMG's Internet Inter-ORB Protocol (IIOP)) and marshaling parameters. ## Using the java.rmi Package Let's take a look at the Java-native remote method system. The following sections provide a more detailed explanation of how the RMI system works and what you need to do to use it. A sample service is developed that provides `java.io.InputStream` and `java.io.OutputStream` compatible access to a file located on a remote machine. ### An Overview of java.rmi The RMI system consists of several different classes and interfaces. The following sections give brief explanations of what the important ones do and how they are used. #### The java.rmi.Remote Interface The RMI system is based on remote interfaces through which a client accesses the methods of a remote object. These interfaces must be declared to extend `java.rmi.Remote`, and each method of an interface must indicate that it throws `java.rmi.RemoteException` in addition to any other exceptions. #### The java.rmi.server.RemoteServer Class The `RemoteServer` provides a superclass for servers that provide remote access to objects. The second step in developing an RMI server is to create a server class that implements your remote interface. This server class should extend one of the subclasses of `RemoteServer` (the `UnicastRemoteObject` is the only `RemoteServer` subclass provided by the RMI system at this time) and must contain the actual code for the methods declared in the remote interface. After the server class has been created, the RMI stub compiler (`rmic`) is given the interface for the class and the server that provides the implementation used to create several "glue" classes. These glue classes work behind the scenes to handle all the nasty details such as contacting the remote virtual machine, passing arguments, and retrieving a return value (if any). #### The java.rmi.Naming Class The `Naming` class provides a way for server classes to make remote objects visible to clients. All the `Naming` class's methods are static and do not require an instance to use. A server that wants to make an object available calls the `bind()` or `rebind()` method with the name of the object (passed as a `String`) and a reference to an object implementing an interface extending `Remote`. Clients can call the `lookup()` method with a `String` representation of the URL for the object they want to access. RMI URLs are of the form `rmi://`host[:port]`/`name, where host is the name of the host on which the object's server resides (with an optional port number) and name is the name of the object. #### Exceptions The RMI package provides several exceptions used to indicate errors during remote method calls. The most common exception is the generic `RemoteException` used to indicate that some sort of problem occurred during a call. All methods of an interface extending the `Remote` interface must note that they can throw this exception. Several other more specific exceptions such as `StubNotFoundException` (thrown when the RMI system cannot find the glue classes generated by `rmic`) and `RemoteRuntimeException` (thrown when a `RuntimeException` occurs on the server during a method call) are subclasses of `RemoteException`. The `Naming` class has two exceptions--`NotBoundException` and `AlreadyBound`--that are thrown to indicate that a given name either hasn't been bound to an object or has already been bound. All the naming methods can also throw a `java.net.UnknownHostException` if the host specified in the URL is invalid; they can throw a `java.net.MalformedURLException` if the given URL is not syntactically correct. ### RMI Example Architecture To demonstrate the `java.rmi` package, we will create a (very simple) file server. This server accepts requests from a remote caller and returns a `RemoteObject` that is used by wrapper classes to provide `java.io.InputStream` and `java.io.OutputStream` objects that read or write from the remote file. ### The RemoteInputHandle Interface First off, we define the interface with which our input wrapper class interacts with the remote file (see Listing 54.1). The `RemoteInputHandle` class provides methods that correspond to those required by the `java.io.InputStream` abstract class. The interface simply defines the methods required for an `InputStream` object. Each method can throw a `RemoteException` as noted in the `throws` clause. #### Listing 54.1. The RemoteInputHandle interface.
    import java.rmi.*;
    import java.io.IOException;
    public interface RemoteInputHandle
        extends Remote
    {
      public int available( )
        throws IOException, RemoteException;

      public void close( )
        throws IOException, RemoteException;

      public void mark( int readlimit )
        throws RemoteException;

      public boolean markSupported( )
        throws RemoteException;

      public int read( )
        throws IOException, RemoteException;

      public int read( byte b[] )
        throws IOException, RemoteException;

      public int read( byte b[], int off, int len )
        throws IOException, RemoteException;

      public void reset( )
        throws IOException, RemoteException;

      public long skip( long n )
        throws IOException, RemoteException;
    }
### The RemoteInputHandleImpl Class Next up is the `RemoteInputHandleImpl` class, which provides the implementation for the `RemoteInputHandle` interface just defined (see Listing 54.2). The `RemoteFileServerImpl` class creates a new input handle implementation when a `RemoteInputHandle` is requested. The constructor for the implementation class takes one argument: the `InputStream` for which we are providing remote access. This makes the handle more useful because we can provide remote access to any local object that extends `InputStream`. This stream is saved in an instance variable (`inStream`) after the `UnicastRemoteServer` superclass's constructor is called. The superclass constructor is called because it has to set things up to listen for requests from remote clients. #### Listing 54.2. The RemoteInputHandleImpl class.
    import java.rmi.*;
    import java.rmi.server.UnicastRemoteObject;
    import java.rmi.RMISecurityManager;

    public class RemoteInputHandleImpl
        extends UnicastRemoteObject
        implements RemoteInputHandle
    {
      private InputStream inStream;
      public RemoteInputHandleImpl( InputStream in )
        throws RemoteException
      {
        super( );

        inStream = in;
      }
Next comes the actual code implementing the methods of the `RemoteInputHandle` interface (see Listing 54.3). Each method simply calls the corresponding method on `inStream` and returns the return value from that call (as appropriate). The RMI system takes care of returning the result--as well as any exceptions that occur--to the calling object on the remote machine. #### Listing 54.3. The methods of the RemoteInputHandleImpl class.
      public int available( )
        throws IOException, RemoteException
      {
        return inStream.available();
      }

      public void close( )
        throws IOException, RemoteException
      {
        inStream.close( );
      }

      public synchronized void mark( int readlimit )
        throws RemoteException
      {
        inStream.mark( readlimit );
      }

      public boolean markSupported( )
        throws RemoteException
      {
        return inStream.markSupported( );
      }

      public int read( )
        throws IOException, RemoteException
      {
        return inStream.read( );
      }

      public int read( byte b[] )
        throws IOException, RemoteException
      {
        return inStream.read( b );
      }

      public int read( byte b[], int off, int len )
        throws IOException, RemoteException
      {
        return inStream.read( b, off, len );
      }

      public synchronized void reset( )
        throws IOException, RemoteException
      {
        inStream.reset( );
      }

      public long skip( long n )
        throws IOException, RemoteException
      {
        return inStream.skip( n );
      }
    }
### The RemoteInputStream Class The `RemoteInputStream` class extends the abstract `InputStream` class and uses the `RemoteInputHandle` interface. The constructor first contacts a `RemoteFileServer` to obtain a `RemoteInputHandle` reference for the path given and then stores this handle in an instance variable. The `InputStream` methods are mapped into the corresponding calls on the `RemoteInputHandle` (that is, the `RemoteInputStream` `read()` method calls the `read()` method on the `RemoteInputHandle` reference obtained by the constructor). NOTE: You may wonder why we are using a wrapper class when all it does is turn around and call the same method on the interface. The reason is that we want to provide a class that can be used any place an `InputStream` or `OutputStream` can be used. Although this approach increases the overhead because we have to make an extra method call, the ability to use our remote streams as drop-in replacements outweighs the cost of that extra call. For example, you can create a `PrintStream` using a `RemoteOutputStream` for a log file for an application. Anything you print to this `PrintStream` is written to the log file on the remote machine. Without the wrapper class, you would have to individually extend each class to use the `RemoteInputHandle` or `RemoteOutputHandle` as needed. We'll start out with the necessary imports and the class definition (see Listing 54.4). We need access to the `java.io` classes because the `RemoteInputStream` extends `InputStream`. We also need access to the RMI `Naming` class so that we can use the `lookup()` method to get a `RemoteInputHandle` from the server. There are two constructors for the class: One takes a path name as the argument and contacts the file server residing on the same host, and the other takes a remote host name to contact as well. #### Listing 54.4. The RemoteInputStream class.
    import java.io.*;
    import java.rmi.RemoteException;
    import java.rmi.Naming;
    import java.rmi.NotBoundException;

    public class RemoteInputStream
        extends InputStream
    {
      private RemoteInputHandle in;

      public RemoteInputStream( String path )
        throws IOException, RemoteException, NotBoundException
      {
        String url = "rmi://localhost/RFSI";
        RemoteFileServer rfs = (RemoteFileServer) Naming.lookup( url );
        in = rfs.getInStream( path );
      }

      public RemoteInputStream( String path, String host )
        throws IOException, RemoteException, NotBoundException
      {
        String url = "rmi://" + host + "/RFSI";
        RemoteFileServer rfs = (RemoteFileServer) Naming.lookup( url );
        in = rfs.getInStream( path );
      }
Next, each of the `InputStream` methods is defined (see Listing 54.5). The code for each method tries to call the corresponding method on the handle object. If a `RemoteException` occurs, an `IOException` is thrown with the message from the `RemoteException` as its message. #### Listing 54.5. The InputStream methods of the RemoteInputStream class.
      public int available( )
        throws IOException
      {
        try {
          return in.available( );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public void close( )
        throws IOException
      {
        try {
          in.close( );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public synchronized void mark( int readlimit )
      {
        try {
          in.mark( readlimit );
        } catch( Exception e ) {
          System.err.println(
               "RemoteInputStream::mark: Remote error: " + e );
        }
      }

      public boolean markSupported( ) {
        try {
          return in.markSupported( );
        } catch( RemoteException e ) {
          return false;          // Assume mark not supported
        }
      }

      public int read( )
        throws IOException
      {
        try {
          return in.read( );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public int read( byte b[] )
        throws IOException
      {
        try {
          return in.read( b );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public int read( byte b[], int off, int len )
        throws IOException
      {
        try {
          return in.read( b, off, len );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public synchronized void reset( )
        throws IOException
      {
        try {
          in.reset( );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }

      public long skip( long n )
        throws IOException
      {
        try {
          return in.skip( n );
        } catch( RemoteException e ) {
          throw new IOException( "Remote error: " + e );
        }
      }
    }
### The Output Side Because the remote interface, implementation, and the wrapper class for the output stream version are, for the most part, identical to those for input stream, they are not given here. The methods in the interface correspond to those for `java.io.OutputStream` instead of `InputStream`, and the `RemoteOutputStream` object extends `OutputStream`. The complete code for all the output classes is contained on the CD-ROM that accompanies this book. ### The RemoteFileServer Interface and the RemoteFileServerImpl Class The `RemoteFileServer` interface provides two methods that the remote input and output stream classes use to obtain handles (see Listing 54.6). #### Listing 54.6. The RemoteFileServer interface.
    public interface RemoteFileServer
        extends java.rmi.Remote
    {

      public RemoteOutputHandle getOutStream( String path )
        throws java.rmi.RemoteException;

      public RemoteInputHandle getInStream( String path )
        throws java.rmi.RemoteException;
    }
The server itself is very simple. It consists of a constructor that calls the `UnicastRemoteServer` superclass, a method that does some sanity checking on the path names requested, implementations of the interface methods, and a `main()` method that allows the server to be started (see Listing 54.7). We start off as usual with the `import` statements, class declaration, and the constructor. Note that there is a `static` class variable `PATH_SEPARATOR`, which should be changed to whatever character separates directory components on your operating system. #### Listing 54.7. The RemoteFileServerImpl class.
    import java.io.*;
    import java.rmi.*;
    import java.rmi.server.UnicastRemoteServer;
    import java.rmi.server.StubSecurityManager;

    public class RemoteFileServerImpl
        extends UnicastRemoteServer
        implements RemoteFileServer
    {
      // Path component separator.  Change as apropriate to your OS.
      public static char PATH_SEPARATOR = '/';

      public RemoteFileServerImpl( )
        throws RemoteException
      {
        super( );     // Call superclass' constructor
        // No class specific initialisation needed.
      }
The `checkPathName()` method shown in Listing 54.8 does some rudimentary checking to ensure that the path name does not point outside the current directory or one of its subdirectories. The code that checks for an absolute path (that is, a path that starts at the root directory or with a specific drive) should be edited as appropriate for your platform. #### Listing 54.8. The RemoteFileServerImpl.checkPathName() method.
      public boolean checkPathName( String path )
      {
        // No absolute path names (i.e. ones beginning with a slash or drive)
        // UNIX Version
        if( path.charAt( 0 ) == PATH_SEPARATOR ) {
          return false;
        }
         // Wintel Version
        /*
           if( path.charAt( 1 ) == ':' && path.charAt( 2 ) == PATH_SEPARATOR ) {
              return false;
           }
        */

    // No references to parent directory with ".."
        for( int i = 0; i < path.length() - 1; i++ ) {
          if( path.charAt( i ) == '.'
              && path.charAt( i + 1 ) == '.' ) {
             return false;
          }
        }
        return true;     // Path's OK
      }
Next comes the code implementing the methods of our remote interface (see Listing 54.9). Each calls `checkPathName()` on the path and then tries to open either a `FileInputStream` or `FileOutputStream` as appropriate. Any exception that occurs while obtaining a stream is rethrown as a `RemoteException` (although there is no reason the interface cannot throw the appropriate exceptions). Once the stream has been opened, a `RemoteInputHandleImpl` or `RemoteOutputHandleImpl` object is created as appropriate with the just-opened stream. The handle is then returned to the caller. #### Listing 54.9. The methods of the RemoteFileServerImpl class.
      public RemoteInputHandle getInStream( String path )
        throws java.rmi.RemoteException
      {
        FileInputStream file = null; // Used to hold file for input

        // Log that we're opening a stream
        System.err.println( "RFSI::getInStream( \"" + path + "\" )" );

        // Check that the path name is legal or gripe
        if( !checkPathName( path ) ) {
          RemoteException e =
            new RemoteException( "Invalid pathname '" + path + "'." );
          throw e;
        }

        // Try and open a FileInputStream for the path
        try {
          file = new FileInputStream( path );
        } catch( FileNotFoundException e ) {
          // File doesn't exist, so throw remote exception with that message
          RemoteException r =
            new RemoteException( "File does not exist: "
                                + e.getMessage() );
          throw r;
        } catch( IOException e ) {
          // Problem opening file, so throw exception saying that
          RemoteException r =
            new RemoteException( "Error opening file: "
                                + e.getMessage() );
          throw r;
        }

        // Return value is a RemoteInputHandle for an RIH implementation
        // object created with the file we just opened as it's input stream.
        RemoteInputHandle retval =
          new RemoteInputHandleImpl( file );
        return retval;      // Return handle to caller
      }

      public RemoteOutputHandle getOutStream( String path )
        throws java.rmi.RemoteException
      {
        FileOutputStream file = null; // Used to hold file for output
        // Log that we're opening a stream
        System.err.println( "RFSI::getOutStream( \"" + path + "\" )" );
        // Check that the path name is legal or gripe
        if( !checkPathName( path ) ) {
          RemoteException e =
            new RemoteException( "Invalid pathname '" + path + "'." );
          throw e;
        }

        // Try and open FileOutputStream for the path
        try {
          file = new FileOutputStream( path );
        } catch( IOException e ) {
          // Problem opening file for output, so throw exception saying so
          RemoteException r =
            new RemoteException( "Error opening file: "
                                + e.getMessage() );
          throw r;
        }

        // Return value is a RemoteOutputHandle for an ROH implementation
        // object created with the file just opened as it's output stream
        RemoteOutputHandle retval = new RemoteOutputHandleImpl( file );
        return retval;     // Return the handle
      }
Finally, we come to the `main()` method, which can be used to start a standalone server from the command line (see Listing 54.10). The first thing this method does is to create a `StubSecurityManager`--a `SecurityManager` context appropriate for a standalone remote object server. Next, `main()` creates a `RemoteFileServerImpl` object and binds it to the name `RFSI`. If an exception occurs during object creation or binding, the name of the exception is noted and the server exits. #### Listing 54.10. The RemoteFileServerImpl.main() method.
      public static void main( String args[] )
      {
        // Create and install stub security manager
        System.setSecurityManager( new StubSecurityManager( ) );

        try {
          System.err.println( "RFSI::main: creating RFSI." );

          // Create a new server implementation object
          RemoteFileServerImpl i = new RemoteFileServerImpl( );

          // Bind our server object to a name so clients may contact us.
          /* The URL will be "rmi://host/RFSI", with host replaced with */
          // the name of the host we're running on.
          String name = "RFSI";
          System.err.println( "RFSI::main: binding to name: " + name );
          Naming.rebind( name, i );
        } catch( Exception e ) {
          // Problem creating server.  Log exception and die.
          System.err.println( "Exception creating server: "
                             + e + "\n" );
          e.printStackTrace( System.err );
          System.exit( 1 );
        }
      }
    }
### The rfsClient Class To demonstrate how to use our remote files, we now develop a very simple client that opens a remote output file and writes a message to it (see Listing 54.11). An input stream is obtained and the stream's contents are read back. The output filename is defined as `outputfile` and the input filename defaults to `inputfile` (however, if an argument is given on the command line, that name is used instead). The host contacted is defined as the local host, but you can change the URL used to point to a remote machine if you have access to more than one host. #### Listing 54.11. The rfsClient class.
    import java.io.*;
    import java.rmi.*;

    public class rfsClient
    {
      public static void main( String args[] ) {
        System.setSecurityManager( new java.rmi.RMISecurityManager() );

        // Contact remote file server running on same machine
        String url = "rmi://localhost/";

        // Default name of file to read
        String infile = "inputfile";

        // Try and open an output stream to a file called "outputfile"
        try {
          OutputStream out = new RemoteOutputStream( "outputfile");
          PrintWriter ps = new PrintWriter( out );
          ps.println( "Testing println on remote file." );
          ps.println( new java.util.Date() );
          ps.flush();
          ps.close();
          ps = null;
          out = null;
        } catch( Exception e ) {
          System.err.println( "Error on getOutStream: " + e );
          e.printStackTrace();
          System.exit( 1 );
        }

        // If we were given a command line argument, use that as the
        // input file name
        if( args.length != 0 ) {
          infile = args[ 0 ];
        }

        // Try and open an output stream on a file
        try {
          InputStream in = new RemoteInputStream( infile );
          DataInputStream ds = new DataInputStream( in );
          // Read each line of the file and print it out
          try {
            String line = ds.readLine( );
            while( line != null ) {
              System.err.println( "Read: " + line );
              line = ds.readLine( );
          } catch( EOFException e ) {
            System.err.println( "EOF" );
          }
        } catch( Exception e ) {
          System.err.println( "Error on getInStream: " + e );
          e.printStackTrace( );
          System.exit( 1 );
        }
        System.exit( 0 );     // Exit gracefully
      }
    }
## Using the IDL Compiler The following sections cover the CORBA-based IDL compiler and support classes. First, we'll explain exactly what the IDL compiler does and how it maps objects from the IDL definitions to Java equivalents. A simple example using the IDL system is also given. ### The IDL-to-Java Compiler The IDL compiler takes an object definition in the CORBA IDL format and generates a Java version. Table 54.1 shows several of these mappings. #### Table 54.1. Sample IDL-to-Java mappings.
IDL Feature | Java Mapping
`module` | `package`
`boolean` | `boolean`
`char` | `char`
`octet` | `byte`
`string` | `java.lang.String`
`short` | `short`
`long` | `int`
`long long` | `long`
`float` | `float`
`enum` | A Java class with a `static final int` member for each `enum` member
`struct` | A Java class; all methods and instance variables are `public`
`interface` | A Java interface; the compiler also generates a stub that implements the interface if you choose
`exception` | A Java class that extends the `omg.corba.UserException` class
As Table 54.1 shows, most of the mappings are straightforward. Some IDL constructs do not have a direct Java equivalent. One example of this is a `union` (a structure that can hold one of several different types of components); in this case, the `union` is a class with a discriminator (which indicates the type of information the union currently holds) and access methods for each possible content type. Another difference between Java and the IDL model is in the way parameters are passed to method calls. Java passes all parameters by value; the IDL model defines three ways parameters can be passed: `in` (which is a pass-by value, just as it is with Java); `out` (which is a pass-by reference--meaning that the parameter is passed so that it can be modified); and `inout` (which is a cross between a pass-by value and a pass-by reference--the value remains the same until the method returns). ### IDL Support Classes With the `java.rmi` system, the remote objects are themselves servers. The CORBA model depends on a separate request broker to receive requests and actually call methods. The IDL system provides a class to represent the ORB. The generic ORB object provides a very important method: `resolve()`. This method takes a URL and a remote object reference created from a stub class of the appropriate type and binds the reference to the server. The format for IDL URLs is `idl:`orb package`://`hostname[:port]`/`object, where orb package is the Java package that provides access to a given type of ORB (`sunw.door` is the simple ORB that comes with the IDL package, `sunw.neo` is Sun's NEO ORB, and so on); hostname and the optional port are used to determine where to contact the ORB; and object is the name of the object requested from the ORB. The CORBA runtime system also defines mappings from the standard CORBA exceptions to Java exceptions. All IDL-defined exceptions are subclasses of one of two classes: `sunw.corba.SystemException` (for all exceptions raised by the CORBA system itself) and `sunw.corba.UserException` (used as the superclass for all user-defined exceptions in IDL objects). ### IDL System Example To demonstrate the use of the IDL compiler and the CORBA interface, we will develop a simple example. Our object will represent a conference room. The goal is to allow clients to query the state of the room (whether it is available or in use). #### Room IDL Definition The first step in creating our example is to write the IDL definition for the object. The interface defines an enumerated type, `roomStatus`, which notes whether the room is in use or available. A room has (for our purposes) two attributes: a name and a status. The interface also provides a method to set the status of the room. The IDL-to-Java compile (`idlgen`) takes this definition and creates several interfaces and classes. These classes are placed in a package called `unleashed`. The classes implementing the `roomStatus` enumeration are placed in the `unleashed.Room` package as shown in Listing 54.12. #### Listing 54.12. The IDL for a room (room.idl).
    module unleashed {
      interface Room {
        // Status of a room
        enum roomStatus { available, inUse };

        // Room name
        readonly attribute string Name;

        // Current room status
        readonly attribute roomStatus Status;

        // Method to set the status of the Room
        void setStatus( in roomStatus newStatus );
      };
    };
#### The RoomImpl Class The first class that must be defined is the implementation object for the room object (see Listing 54.13). This is analogous to creating a subclass of `RemoteServer` when using the RMI classes. Unlike the RMI system, however, the server is a separate ORB object. The implementation must implement the `unleashed.RoomServant` interface (which was generated by `idlgen`). The `RoomImpl` class has two instance variables to hold the name of the room and its status. The constructor takes two arguments, which are copied into these instance variables. Normally, each attribute of an interface has a `get()` and `set()` method defined for it (`get`Attribute`()` and `set`Attribute`()`). Because both of the attributes on the room interface are read-only, only the `getName()` and `getStatus()` methods are defined by the compiler. Our implementation simply returns the contents of the instance variables. The `setStatus()` method likewise performs a bounds check (using the enumeration class created by the compiler) to set the `status` member. #### Listing 54.13. The unleashed.RoomImpl class.
    package unleashed;

    public class RoomImpl implements unleashed.RoomServant
    {
      private String name;
      private int status;
      public RoomImpl( String n, int s )
        throws sunw.corba.EnumerationRangeException
      {
        name = n;
        status = unleashed.Room.roomStatus.narrow( s );
      }
      public String getName( ) {
        return name;
      }
      public int getStatus( ) {
        return status;
      }
      public void setStatus( int newStatus ) {
        status = unleashed.Room.roomStatus.narrow( newStatus );
      }
    }
#### The RoomServer Class Now that we have the implementation for the room object, we have to create a server class (see Listing 54.14). The server uses the simple `sunw.door` ORB that comes with the IDL system. It first calls `sunw.door.Orb.initialize()` to start the ORB listening for requests from clients. An implementation object is created and passed to the `RoomSkeleton.createRef()` method. This method, created by the IDL compiler, returns a `RoomRef` suitable for passing to the ORB's `publish()` method. This accomplishes the same thing as using the `java.rmi.Naming.bind()` method--that is, binding the object reference to a name accessible by a URL. #### Listing 54.14. The RoomServer class.
    package unleashed;

    public class RoomServer
    {
      static String pathName = "room.server";
      public static void main( String arg[] ) {
        sunw.door.Orb.initialize( );
        try {
          RoomRef r =
            RoomSkeleton.createRef(
              new RoomImpl( "Room 203", unleashed.Room.roomStatus.available ) );
          sunw.door.Orb.publish( pathName, r );
        } catch( sunw.door.naming.Failure e ) {
          System.err.println( "Couldn't bind object in naming context: " + e );
          System.exit( 1 );
        }
        System.err.println( "Room server setup and bound on port "
                           + sunw.door.Orb.getDefaultPort() );
      }
    }
#### The Client Applet The last step is to create a client to access the remote object. `RoomClient` is an applet that connects to a room object and provides a way of querying the current status and changing the status. An instance variable of type `unleashed.RoomRef` is used to hold the currently active remote object reference. The `init()` method creates the user interface. A `TextField` is created to allow the user to enter the host name to connect to. Fields are also created to show the name and status of the room once a server has been contacted. Finally, three buttons are created: one to cause the applet to connect to the room object, one to request that the room be reserved (marked as "in use"), and one to request that the room be released (marked as "available"). The `connect()` method uses the host name entered by the user to construct a URL for the room server residing on that machine. The URL assumes that the server is running on the default `sunw.door` ORB port. The `connect()` method creates a reference to a `RoomStub` and uses the `sunw.corba.Orb.resolve()` method to resolve the URL to an object reference. If an exception occurs, the error message is displayed in the applet's status area and printed to `System.err`. The `updateStatus()` method uses the room reference obtained by `connect()` to determine the name and status of the room. The information is printed to the corresponding field of the interface. Any exceptions are noted in the status line and logged to `System.err`. Both `reserve()` and `release()` call the `setStatus()` method on the `RoomRef` object. The only difference between the two methods is the constant from the `unleashed.Room.roomStatus` class they use. Finally, the `action()` method is called whenever the user clicks one of the buttons. This method determines which button was clicked and calls the corresponding method. Listing 54.15 shows the complete `RoomClient` class; Figure 54.1 shows the `RoomClient` applet in use. #### Listing 54.15. The RoomClient class.
    import java.net.URL;
    import java.awt.*;
    import java.applet.Applet;
    public class RoomClient extends Applet
    {
      unleashed.RoomRef r;
      String serverUrl;
      TextField nameField;
      TextField statusField;
      TextField hostField;
      Button connectButton;
      Button reserveButton;
      Button releaseButton;

      public void init( ) {
        Panel p;
        setLayout( new BorderLayout() );
        p = new Panel();
        p.setLayout( new FlowLayout() );
        p.add( new Label( "Host: " ) );
        p.add( hostField = new TextField( 30 ) );

        add( "North", p );

        Panel stats = new Panel();
        stats.setLayout( new GridLayout( 2, 1 ) );
        p = new Panel( );
        p.setLayout( new GridLayout( 1, 2 ) );
        p.add( new Label( "Room Name: " ) );
        p.add( nameField = new TextField( 30 ) );
        stats.add( p );
        p = new Panel( );
        p.setLayout( new GridLayout( 1, 2 ) );
        p.add( new Label( "Room status: " ) );
        p.add( statusField = new TextField( 10 ) );
        stats.add( p );

        add( "Center", stats );

        p = new Panel( );
        p.setLayout( new GridLayout( 1, 3 ) );
        p.add( connectButton = new Button( "Connect" ) );
        p.add( reserveButton = new Button( "Reserve" ) );
        p.add( releaseButton = new Button( "Release" ) );

        add( "South", p );

        // Name and status fields not editable
        nameField.setEditable( false );
        statusField.setEditable( false );
        updateStatus();
      }

      public void connect( ) {
        String host = hostField.getText();
        if( host == null || host.length() == 0 ) {
          showStatus( "Enter a hostname first." );
          return;
        }

        serverUrl =
          "idl:sunw.door://"
            + host + ":" + sunw.door.Orb.getDefaultPort()
              + "/room.server";
        showStatus( "Connecting to room server on " + host );

        try {
          r = unleashed.RoomStub.createRef();
          sunw.corba.Orb.resolve( serverUrl, r );
        } catch( Exception e ) {
          System.err.println( "Couldn't resolve: " + e );
          showStatus( "Couldn't resolve room server: " + e );
          return;
        }
        updateStatus( );
      }

      public void updateStatus( ) {
        if( r == null ) {
          nameField.setText( "" );
          statusField.setText( "" );
          showStatus( "Not Connected" );
          return;
        }

        // Get room name and stick it in text field
        try {
          String name = r.getName();
          nameField.setText( name );
        } catch( sunw.corba.SystemException e ) {
          System.err.println( "Error getting room name: " + e );
          showStatus( "Error getting room name: " + e );
        }

        try {
          switch( r.getStatus( ) ) {
          case unleashed.Room.roomStatus.available:
            statusField.setText( "available" );
            break;
          case unleashed.Room.roomStatus.inUse:
            statusField.setText( "in use" );
            break;
          }
        } catch( sunw.corba.SystemException e ) {
          System.err.println( "Error getting room status: " + e );
          showStatus( "Error getting room status: " + e );
        }
      }

      public void reserve( ) {
        if( r == null ) {
          showStatus( "You must connect to a server first." );
          return;
        }

        try {
          r.setStatus( unleashed.Room.roomStatus.inUse );
        } catch( sunw.corba.SystemException e ) {
          System.err.println( "Error setting room status: " + e );
          showStatus( "Error reserving room: " + e );
        }
        updateStatus( );
      }

      public void release( ) {
        if( r == null ) {
          showStatus( "You must connect to a server first." );
          return;
        }

        try {
          r.setStatus( unleashed.Room.roomStatus.available );
        } catch( sunw.corba.SystemException e ) {
          System.err.println( "Error setting room status: " + e );
          showStatus( "Error reserving room: " + e );
        }
        updateStatus( );
      }

      public boolean action( Event e, Object o )
      {
        if( "Connect".equals( o ) ) {
          connect();
          return true;
        }
        if( "Reserve".equals( o ) ) {
          reserve( );
          return true;
        }
        if( "Release".equals( o ) ) {
          release( );
          return true;
        }
        return false;
      }
    }
Figure 54.1. The `RoomClient` applet in action. ## Summary You should now have an idea how both of the distributed object systems for Java work. Both systems have their advantages and disadvantages. Hopefully, you now know enough to choose the one that best fits your application.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.