All Categories :
Java
Chapter 45
Remote Objects and the Java IDL System
by Mike Fletcher
CONTENTS
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 Broker Request 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 systems is given
first, followed by examples using both.
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 means 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 in 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, with
Web browsers being the client and Web sites 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 of
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 which 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 is no stranger to solving such problems. Their 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 hostnames 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. 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 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 Broker Request 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 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.
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. An example
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. This interface must be
declared to extend java.rmi.Remote,
and each method of the interface must indicate that it throws
a 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
(UnicastRemoteServer is the
only subclass provided with 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 means 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
hostname the object's server resides on (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 the 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, which it
throws to indicate that a given name 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 also 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 by which our input wrapper
class interacts with the remote file (see Listing 45.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 45.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 45.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 45.2. The RemoteInputHandleImpl
class.
import java.rmi.*;
import java.rmi.server.UnicastRemoteServer;
import java.rmi.server.StubSecurityManager;
public class RemoteInputHandleImpl
extends UnicastRemoteServer
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 45.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 45.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.
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 45.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 pathname as the argument and contacts the file server
residing on the same host, and the other takes a remote hostname
to contact as well.
Listing 45.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 );
}
Each of the InputStream methods
is defined next (see Listing 45.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 45.5. The InputStream
methods of the RemoteInputStream
class.
public int availabe( )
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
The remote interface, implementation, and the wrapper class for
the output stream version are, for the most part, identical to
those for input so 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 RemoteFileServerImpl
Class
The RemoteFileServer interface
provides two methods that the remote input and output stream classes
use to obtain handles (see Listing 45.6).
Listing 45.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 pathnames
requested, implementations of the interface methods, and a main()
method that allows the server to be started (see Listing 45.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 45.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 appropriate for 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 45.8 does some rudimentary checking to ensure
that the pathname 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 45.8. The RemoteFileServerImpl.checkPathName() method.
Public boolean checkpathname( String path )
{
// No absolute pathnames (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 45.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 45.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 pathname 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 pathname 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 45.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 45.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 45.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 45.11. The rfsClient
class.
import java.io.*;
import java.rmi.*;
public class rfsClient
{
public static void main( String args[] ) {
System.setSecurityManager(
new java.rmi.server.StubSecurityManager() );
// 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");
PrintStream ps = new PrintStream( out );
ps.println( "Testing println on remote file." );
ps.println( new java.util.Date() );
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
}
}
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 their 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 45.1 shows several of these
mappings.
Table 45.1. Example 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 45.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, a class with a discriminator (which indicates what
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 may be passed:
in (which is a pass by value,
just as Java does); 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 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,
which is 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).
Note |
This example was created using the Alpha 2 release of the IDL compiler and CORBA classes. Although Sun is very good about freezing APIs before public releases, some changes may have been made. When in doubt, use the documentation that came with the IDL system as the definitive source.
|
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 45.12.
Listing 45.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 45.13). This is analogous to
creating a subclass of RemoteServer
when using the RMI classes. Unlike the RMI system, 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 (getAttribute()
and setAttribute()).
Because both of the attributes on the room interface are read-only,
only the getName() and getStatus()
methods were 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 45.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 45.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 45.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 means of querying the
current status and changing the status. An instance variable of
unleashed.RoomRef type 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 hostname 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 hostname 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
next 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 presses one of the buttons. This method
determines which button was pressed and calls the corresponding
method. Listing 45.15 shows the complete RoomClient
class; Figure 45.1 shows the RoomClient
applet in use.
Figure 45.1: The RoomClient applet in action.
Listing 45.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;
}
}
You should now have an idea how both of the distributed object
systems for Java work. Both systems have their own advantages
and disadvantages. Hopefully, you now know enough to choose the
one that best fits your application.
Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.