Search the Catalog
Java Enterprise in a Nutshell

Java Enterprise in a Nutshell

A Desktop Quick Reference

By David Flanagan, Jim Farley, William Crawford & Kris Magnusson
1st Edition September 1999
1-56592-483-5, Order Number: 4835
622 pages, $29.95

Chapter 4 Java IDL

Contents:
The CORBA Architecture
Creating CORBA Objects
Putting It in the Public Eye
Finding Remote Objects
What If I Don't Know the Interface?

The Java IDL API, introduced in Version 1.2 of the Java 2 platform, provides an interface between Java programs and distributed objects and services built using the Common Object Request Broker Architecture (CORBA). CORBA is a standard defined by the Object Management Group (OMG). It describes an architecture, interfaces, and protocols that distributed objects can use to interact with each other. Part of the CORBA standard is the Interface Definition Language (IDL), which is an implementation-independent language for describing the interfaces of remote-capable objects. There are standard mappings defined by the OMG for converting IDL interfaces into C++ classes, C code, and Java classes, among other things. These generated classes use the underlying CORBA framework to communicate with remote clients and give you the basis for implementing and exporting your own distributed objects. Java IDL is an implementation of the standard IDL-to-Java mapping and is provided by Sun with the standard Java SDK in the org.omg.CORBA and org.omg.CosNaming packages and their subpackages.[1]

[1] The version of Java IDL shipped with Version 1.2 of Java 2 is compliant with the CORBA 2.x specification.

Like RMI, Java IDL gives you a way to access remote objects over the network. It also provides the tools you need to make your objects accessible to other CORBA clients. If you export a Java class using Java IDL, you can create an instance of that class and publish it through a naming/directory service. A remote client can find this object, call methods on it, and receive data from it, just as if it were running on the client's local machine. Unlike RMI, however, objects that are exported using CORBA can be accessed by clients implemented in any language with an IDL binding (C, C++, Ada, etc.).

The CORBA standard is extensive, to say the least. In addition to the basic remote object architecture and the syntax of IDL, it also includes specifications for several distributed object services, like an object naming service, a security policy service, and persistent object services. It would be foolhardy to attempt to cover all these topics completely in one chapter, so I won't. Instead, I'll just cover the basic features of the CORBA architecture and the IDL syntax. We'll also look at the Naming Service, which is key to almost every CORBA application because it provides a standard way to find remote CORBA objects on the network. With that under our belts, we'll take a look at the Java IDL API and the idltojava compiler and how together they give you an interface from your Java code to CORBA objects and services. They also give you the tools you need to create your own CORBA objects, implemented in Java.

The rest of this chapter is broken down roughly into three parts. In the first part, we'll look at an overview of the CORBA architecture and how it allows you to create, export, access, and manage remote objects. In the second part, we'll explore the details of creating your own CORBA objects. Finally, we'll look at how clients can remotely access your CORBA objects.

4.1 The CORBA Architecture

At its core, the CORBA architecture for distributed objects shares many features with the architecture used by Java RMI. A description of a remote object is used to generate a client stub interface and a server skeleton interface for the object. A client application invokes methods on a remote object using the client stub. The method request is transmitted through the underlying infrastructure to the remote host, where the server skeleton for the object is asked to invoke the method on the object itself. Any data resulting from the method call (return values, exceptions) is transmitted back to the client by the communication infrastructure.

But that's where the similarities between CORBA and RMI end. CORBA was designed from the start to be a language-independent distributed object standard, so it is much more extensive and detailed in its specification than RMI is (or needs to be). For the most part, these extra details are required in CORBA because it needs to support languages that have different built-in features. Some languages, like C++, directly support objects, while others, like C, don't. The CORBA standard needs to include a detailed specification of an object model so that nonobject-oriented languages can take advantage of CORBA. Java includes built-in support for communicating object interfaces and examining them abstractly (using Java bytecodes and the Java Reflection API). Many other languages do not. So the CORBA specification includes details about a Dynamic Invocation Interface and a Dynamic Skeleton Interface, which can be implemented in languages that don't have their own facilities for these operations. In languages that do have these capabilities, like Java, there needs to be a mapping between the built-in features and the features as defined by the CORBA specification.

The rest of this section provides an overview of the major components that make up the CORBA architecture: the Interface Definition Language, which is how CORBA interfaces are defined; the Object Request Broker (ORB), which is responsible for handling all interactions between remote objects and the applications that use them; the Naming Service, a standard service in CORBA that lets remote clients find remote objects on the network; and the inter-ORB communication that handles the low-level communication between processes in a CORBA context.

4.1.1 Interface Definition Language

The Interface Definition Language provides the primary way of describing data types in CORBA. IDL is independent of any particular programming language. Mappings, or bindings, from IDL to specific programming languages are defined and standardized as part of the CORBA specification. At the time of this writing, standard bindings for C, C++, Smalltalk, Ada, COBOL, and Java have been approved by the OMG. Chapter 10, IDL Reference, contains a complete description of IDL syntax.

The central CORBA functions, services, and facilities, such as the ORB and the Naming Service, are also specified in IDL. This means that a particular language binding also provides the bindings for the core CORBA functions to that language. Sun's Java IDL API follows the Java IDL mapping defined by the OMG. This allows you to run your CORBA-based Java code in any compliant Java implementation of the CORBA standard, provided you stick to standard elements of the Java binding. Note, however, that Sun's implementation includes some nonstandard elements; they are highlighted in this chapter where appropriate.

4.1.2 Object Request Broker

The core of the CORBA architecture is the Object Request Broker, as shown in Figure 4.1. Each machine involved in a CORBA application must have an ORB running in order for processes on that machine to interact with CORBA objects running in remote processes. Object clients and servers make requests through their ORBs; the ORB is responsible for making the requests happen or indicating why they cannot. The client ORB provides a stub for a remote object. Requests made on the stub are transferred from the client's ORB to the ORB servicing the implementation of the target object. The request is passed onto the implementation through its skeleton interface.

Figure 4.1: Basic CORBA architecture

Figure 4.1

4.1.3 The Naming Service

The CORBA Naming Service provides a directory naming structure for remote objects. The tree always starts with a root node, and subnodes of the object tree can be defined. Actual objects are stored by name at the leaves of the tree. Figure 4.2 depicts an example set of objects[2] registered within a Naming Service directory. The fully qualified name of an object in the directory is the ordered list of all of its parent nodes, starting from the root node and including the leaf name of the object itself. So, the full name of the object labeled "Fred" is "living thing," "animal," "man," "Fred," in that order.

[2] Example adapted from Categories, by Aristotle. Please pardon the categorization "man," as opposed to "human." This is the typical translation of Aristotle's original Greek, perhaps because political correctness wasn't in fashion in 350 B.C.

Figure 4.2: A naming directory

Figure 4.2

Each branch in the directory tree is called a naming context, and leaf objects have bindings to specific names. The org.omg.CosNaming.NamingContext interface represents each branch in the naming directory. Each NamingContext can be asked to find an object within its branch of the tree by giving its name relative to that naming context. You can get a reference to the root context of the naming directory from an ORB using the resolve_initial_references() method. The standard name for the Naming Service is "NameService", so the following code snippet gets the root NamingContext:

ORB myORB = ORB.init(...);
org.omg.CORBA.Object nameRef =
	myORB.resolve_initial_references("NameService");
NamingContext nc = NamingContextHelper.narrow(nameRef);

Note that we have to narrow the Object reference to a NamingContext reference using the NamingContextHelper.narrow() method. Even though Java has a cast operation in its syntax, there's no guarantee in the Java IDL binding that the object reference returned by the resolve_initial_references() method is the correct type, since there's no guarantee that the local environment has access to the language-specific definition of the object's interface.

This narrow() operation highlights one of the key differences between RMI and CORBA. In the Java environment, class bytecodes are portable, and all remote object types are objects that can be specified by their full class names. An RMI client can automatically download the bytecodes for a remote stub from the object server, if the class for the stub cannot be found locally (see Chapter 3, Remote Method Invocation, for more details on the mechanics of remote class loading). CORBA is a language-independent remote object scheme, so there is no portable way to specify a remote object's type when a client obtains a stub reference. As a result, the stub reference is initially represented by a basic ObjectImpl object that knows how to forward methods requests to its server object. The client application is forced to "cast" this stub to the correct local type, using the appropriate narrow() method. In the Java mapping of IDL, this means calling the narrow() method on the corresponding helper class. The narrow() method converts the reference, making a type-specific stub interface that also includes the remote object reference information needed to forward method requests to the actual object implementation.

4.1.4 Inter-ORB Communication

Version 2.0 (and later) of the CORBA standard includes specifications for inter-ORB communication protocols that can transmit object requests between various ORBs running on the network. The protocols are independent of the particular ORB implementations running at either end of the communication link. An ORB implemented in Java can talk to another ORB implemented in C, as long as they're both compliant with the CORBA standard and use a standard communication protocol. The inter-ORB protocol is responsible for delivering messages between two cooperating ORBs. These messages might be method requests, return types, error messages, etc. The inter-ORB protocol also deals with differences between the two ORB implementations, like machine-level byte ordering and alignment. As a CORBA application developer, you shouldn't have to deal directly with the low-level communication protocol between ORBs. If you want two ORBs to talk to each other, you just need to be sure that they both speak a common, standard inter-ORB protocol.

The Internet Inter-ORB Protocol (IIOP) is an inter-ORB protocol based on TCP/IP. TCP/IP is by far the most commonly used network protocol on the Internet, so IIOP is the most commonly used CORBA communication protocol. There are other standard CORBA protocols defined for other network environments, however. The DCE Common Inter-ORB Protocol (DCE-CIOP), for example, allows ORBs to communicate on top of DCE-RPC.

4.2 Creating CORBA Objects

Now that you understand the various parts of the CORBA architecture, let's walk through the creation of CORBA objects using Java IDL. In order to distribute a Java object over the network using CORBA, you have to define your own CORBA-enabled interface and it implementation. This involves doing the following:

4.2.1 An IDL Primer

This section provides a quick overview of writing a CORBA interface in IDL. A full reference on IDL syntax is provided in Chapter 10, if you need more details.

The syntax of both Java and IDL were modeled to some extent on C++, so there are a lot of similarities between the two in terms of syntax. Interfaces in IDL are declared much like classes in C++ and, thus, classes or interfaces in Java. The major differences between IDL and Java are:

4.2.1.1 Modules

Modules are declared in IDL using the module keyword, followed by a name for the module and an opening brace that starts the module scope. Everything defined within the scope of this module (interfaces, constants, other modules) falls within the module and is referenced in other IDL modules using the syntax modulename::x. Suppose that you want all your classes to be contained in a module called corba, which is part of a larger module called jen (an acronym for the title of this book). In IDL this is declared as follows:


// IDL
module jen {
  module corba {
    interface NeatExample ...
  };
};

If you want to reference the NeatExample interface in other IDL files, use the syntax jen::corba::NeatExample, which may look familiar to readers who have done C++ programming. Java programmers should note the semicolons following the closing braces on the module definitions, which are required in IDL but not in Java. A semicolon is also required after the close of an interface definition.

4.2.1.2 Interfaces

Interfaces declared in IDL are mapped into classes or interfaces in Java. As I mentioned before, IDL is used only to declare modules, interfaces, and their methods. Methods on IDL interfaces are always left abstract, to be defined in the programming language you use to implement the interfaces.

The declaration of an interface includes an interface header and an interface body. The header specifies the name of the interface and the interfaces it inherits from (if any). Here is an IDL interface header:


interface PrintServer : Server { ...

This header starts the declaration of an interface called PrintServer that inherits all the methods and data members from the Server interface. An IDL interface can inherit from multiple interfaces; simply separate the interface names with commas in the inheritance part of the header.

4.2.1.3 Data members and methods

The interface body declares all the data members (or attributes) and methods of an interface. Data members are declared using the attribute keyword. At a minimum, the declaration includes a name and a type (see Chapter 10 for a complete list of the basic data types available in IDL and the mapping to Java types). The declaration can optionally specify whether the attribute is read-only or not, using the readonly keyword. By default, every attribute you declare is readable and writable (for Java, this means that the IDL compiler generates public read and write methods for it). Here is an example declaration for a read-only string attribute:


readonly attribute string myString;

You declare a method by specifying its name, return type, and parameters, at a minimum. You can also optionally declare exceptions the method might raise, the invocation semantics of the method, and the context for the method call (see Chapter 10 for more details). Here is the declaration for a simple method that returns a string:


string parseString(in string buffer);

This declares a method called parseString() that accepts a single string argument and returns a string value.

4.2.1.4 A complete IDL example

Now let's tie all these basic elements together. Here's a complete IDL example that declares a module within another module, which itself contains several interfaces:


module OS {
  module services {
    interface Server {
      readonly attribute string serverName;
      boolean init(in string sName);
    };

    interface Printable {
      boolean print(in string header);
    };

    interface PrintServer : Server {
      boolean printThis(in Printable p);
    };
  };
};

The first interface, Server, has a single read-only string attribute and an init() method that accepts a string and returns a boolean. The Printable interface has a single print() method that accepts a string header. Finally, the PrintServer interface extends the Server interface (hence inheriting all its methods and attributes) and adds a printThis() method that accepts a Printable object and returns a boolean. In all cases, we've declared our method arguments as input-only (i.e., pass-by-value), using the in keyword.

4.2.2 Turning IDL Into Java

Once you've described your remote interfaces in IDL, you need to generate Java classes that act as a starting point for implementing those remote interfaces in Java using an IDL-to-Java compiler. Every standard IDL-to-Java compiler generates the following Java classes from an IDL interface:

The idltojava tool provided by Sun[3] can also generate two other classes:

[3] Although Java IDL is a standard part of Java 1.2, Sun still offers only the early-access version of its idltojava compiler, which you have to download separately from http://developer.java.sun.com/developer/earlyAccess/jdk12/idltojava.html.

So, in addition to generating a Java mapping of the IDL interface and some helper classes for the Java interface, the idltojava compiler also creates subclasses that act as an interface between a CORBA client and the ORB and between the server-side implementation and the ORB. Chapter 12, Java IDL Tools, provides a complete reference for Sun's idltojava compiler. We use this IDL-to-Java tool in the examples in this chapter. Remember, though, that any Java mapping of the CORBA standard should include its own IDL-to-Java compiler to generate these Java classes from the IDL interfaces you write. In addition, the Java that these tools generate should be compliant with the standard IDL mapping for Java, published by the OMG in the CORBA standard.

4.2.2.1 A simple server class

The IDL interface shown in Example 4.1 is the IDL equivalent of the Java class we defined in Example 3-3 in the RMI chapter. The interface, named ThisOrThatServer, declares two methods, doThis() and doThat(). As in the earlier RMI example, each method accepts a string that specifies what to do and returns a string that indicates what was done. Since this is IDL, the string data type is string, and the parameters are declared as in arguments, since we want them to be passed into the remote method by value.

Example 4.1: A ThisOrThatServer Declared in IDL


    interface ThisOrThatServer {
      string doThis(in string what);
      string doThat(in string what);
    };

We can run the idltojava compiler on this IDL interface using the following command line (Windows version):


D:\>idltojava -fno-cpp ThisOrThatServer.idl

This command creates the five Java classes I just described: a Java version of the interface, a helper class, a holder class, a client stub, and a server skeleton. I had to use the -fno-cpp option on my machine because I don't have a C preprocessor installed for idltojava to use; this option tells the IDL compiler to use an alternate parsing scheme while it converts the IDL to Java (see Chapter 12 for complete details on the command-line arguments for idltojava).

The compiler creates the Java interface shown in Example 4.2, in a file named ThisOrThatServer.java. The mapping is fairly straightforward for this simple example. The interface declaration is mapped directly to a Java interface declaration, with the interface extending the org.omg.CORBA.Object interface. If we had included any module definitions in our IDL specification, they would have been mapped into a package statement at the beginning of the Java file. The IDL string type is converted into the Java String type, and, since they don't require any special handling in a remote method call, the in method parameters in IDL are mapped into regular Java input arguments.

Example 4.2: Java Interface for ThisOrThatServer


/*
 * File: ./THISORTHATSERVER.JAVA
 * From: THISORTHATSERVER.IDL
 * Date: Thu Apr 15 21:42:40 1999
 *   By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

public interface ThisOrThatServer
    extends org.omg.CORBA.Object {
    String doThis(String what)
;
    String doThat(String what)
;
}

You might notice that the IDL compiler has put the semicolons following the method declarations on separate lines. To my knowledge, there's no good reason for this; it's just a quirk of the idltojava tool provided by Sun.

4.2.2.2 The helper class

The compiler also generates a helper class, called ThisOrThatServerHelper, as shown in Example 4.3. As I mentioned earlier, the helper class has methods that let you read and write ThisOrThatServer objects to and from CORBA I/O streams, get the TypeCode for a ThisOrThatServer object, and, most importantly, safely narrow a CORBA Object reference into a ThisOrThatServer reference.

Example 4.3: Helper Class for the ThisOrThatServer


/*
 * File: ./THISORTHATSERVERHELPER.JAVA
 * From: THISORTHATSERVER.IDL
 * Date: Thu Apr 15 21:42:40 1999
 *   By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

public class ThisOrThatServerHelper {
    // It is useless to have instances of this class
    private ThisOrThatServerHelper() { }

    public static void write(org.omg.CORBA.portable.OutputStream out, 
                             ThisOrThatServer that) {
        out.write_Object(that);
    }
    public static ThisOrThatServer 
      read(org.omg.CORBA.portable.InputStream in) {
        return ThisOrThatServerHelper.narrow(in.read_Object());
    }
   public static ThisOrThatServer extract(org.omg.CORBA.Any a) {
     org.omg.CORBA.portable.InputStream in = a.create_input_stream();
     return read(in);
   }
   public static void insert(org.omg.CORBA.Any a, ThisOrThatServer that) {
     org.omg.CORBA.portable.OutputStream out = a.create_output_stream();
     write(out, that);
     a.read_value(out.create_input_stream(), type());
   }
   private static org.omg.CORBA.TypeCode _tc;
   synchronized public static org.omg.CORBA.TypeCode type() {
          if (_tc == null)
             _tc = org.omg.CORBA.ORB.init().create_interface_tc(id(), 
                   "ThisOrThatServer");
      return _tc;
   }
   public static String id() {
       return "IDL:ThisOrThatServer:1.0";
   }
   public static ThisOrThatServer narrow(org.omg.CORBA.Object that)
            throws org.omg.CORBA.BAD_PARAM {
        if (that == null)
            return null;
        if (that instanceof ThisOrThatServer)
            return (ThisOrThatServer) that;
        if (!that._is_a(id())) {
            throw new org.omg.CORBA.BAD_PARAM();
        }
        org.omg.CORBA.portable.Delegate dup = 
          ((org.omg.CORBA.portable.ObjectImpl)that)._get_delegate();
        ThisOrThatServer result = new _ThisOrThatServerStub(dup);
        return result;
   }
}

In the implementation of the narrow() method, we can see how the helper class converts a CORBA Object reference to a reference to a specific type. First, the narrow() method checks to see if the Object parameter is already a ThisOrThatServer object (using the Java instanceof operator), then it checks to see if the object passed in is a null pointer. If neither case is true, the Object should contain a delegate of a ThisOrThatServer object. Every CORBA stub for a remote object contains an internal Delegate object (from the org.omg.CORBA.portable package) that's used by the stub to invoke remote requests. If the object's delegate is a ThisOrThatServer (checked using the objects's _is_a() method), the delegate is used to create a new ThisOrThatServer stub. We'll take a look at the ThisOrThatServer stub class in a bit. If the object doesn't contain a delegate, the is_a() method returns false, and the narrow() method throws a BAD_PARAM exception.

4.2.2.3 The holder class

The compiler generates a holder class for the ThisOrThatServer class, as shown in Example 4.4. The holder class, called ThisOrThatServerHolder, is a wrapper used when ThisOrThatServer objects are called for as out or inout arguments in an IDL method. All holder classes implement the Streamable interface from the org.omg.CORBA.portable package. An ORB knows to pass Streamable objects in method calls using the _read() and _write() methods of the Streamable object; these methods handle whatever serialization the object needs.

Example 4.4: Holder Class for the ThisOrThatServer


/*
 * File: ./THISORTHATSERVERHOLDER.JAVA
 * From: THISORTHATSERVER.IDL
 * Date: Thu Apr 15 21:42:40 1999
 *   By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

public final class ThisOrThatServerHolder
     implements org.omg.CORBA.portable.Streamable{
    //  instance variable 
    public ThisOrThatServer value;
    //  constructors 
    public ThisOrThatServerHolder() {
        this(null);
    }
    public ThisOrThatServerHolder(ThisOrThatServer __arg) {
        value = __arg;
    }

    public void _write(org.omg.CORBA.portable.OutputStream out) {
        ThisOrThatServerHelper.write(out, value);
    }

    public void _read(org.omg.CORBA.portable.InputStream in) {
        value = ThisOrThatServerHelper.read(in);
    }

    public org.omg.CORBA.TypeCode _type() {
        return ThisOrThatServerHelper.type();
    }
}

A holder contains a single instance of a CORBA object (a ThisOrThatServer, in this example). When a holder object is passed into a remote method call as an inout argument, its _write() method is invoked. This method takes the object contained by the holder class, serializes it, and streams it through the ORB to the remote object server. When the remote method call returns, the holder's _read() method is invoked to read the (possibly updated) object from the remote object server, and the holder object replaces its internal value with the updated object.

As an example of using the holder class, let's define another IDL interface that includes a method that uses a ThisOrThatServer as an inout parameter:


// IDL
interface ServerManager {
  boolean updateServer(inout ThisOrThatServer server);
};

The Java interface generated from this IDL interface uses the holder class for the ThisOrThatServer as the type for the corresponding Java method parameter:


// Java
public interface ServerManager
    extends org.omg.CORBA.Object {
    boolean updateServer(ThisOrThatServerHolder server)
;
}

The ThisOrThatServerHolder class has public constructors that let you create a holder from an existing ThisOrThatServer object, so that you can easily pass the object into this kind of method.

4.2.2.4 The client and server stubs

The idltojava compiler generates two more classes from our interface definition: a client stub (_ThisOrThatServerStub) and a base class for a server implementation (_ThisOrThatServerImplBase). The client stub, shown in Example 4.5, implements the generated ThisOrThatServer Java interface and acts as a client-side proxy for a remote ThisOrThatServer object. The stub has implementations of the doThis() and doThat() methods from the interface. Each implementation just generates a request to the ORB to make a remote method call on the server-side object that this stub is a proxy for. The method arguments are bundled up and passed along with the request to the ORB. I'm not going to go into the details of the stub's method implementations because you shouldn't have to worry much about them, but it is enlightening to look at the source code to see how your remote objects do what they do in detail, using the core CORBA functions.

Example 4.5: ThisOrThatServer Stub Class Generated by IDL Compiler


/*
 * File: ./_THISORTHATSERVERSTUB.JAVA
 * From: THISORTHATSERVER.IDL
 * Date: Thu Apr 15 21:42:40 1999
 *   By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

public class _ThisOrThatServerStub
        extends org.omg.CORBA.portable.ObjectImpl
        implements ThisOrThatServer {

    public _ThisOrThatServerStub(org.omg.CORBA.portable.Delegate d) {
          super();
          _set_delegate(d);
    }

    private static final String _type_ids[] = {
        "IDL:ThisOrThatServer:1.0"
    };

    public String[] _ids() { return (String[]) _type_ids.clone(); }

    //  IDL operations
    //      Implementation of ::ThisOrThatServer::doThis
    public String doThis(String what)
 {
           org.omg.CORBA.Request r = _request("doThis");
           r.set_return_type(org.omg.CORBA.ORB.init().get_primitive_tc(
               org.omg.CORBA.TCKind.tk_string));
           org.omg.CORBA.Any _what = r.add_in_arg();
           _what.insert_string(what);
           r.invoke();
           String __result;
           __result = r.return_value().extract_string();
           return __result;
   }
    //      Implementation of ::ThisOrThatServer::doThat
    public String doThat(String what)
 {
           org.omg.CORBA.Request r = _request("doThat");
           r.set_return_type(org.omg.CORBA.ORB.init().get_primitive_tc(
               org.omg.CORBA.TCKind.tk_string));
           org.omg.CORBA.Any _what = r.add_in_arg();
           _what.insert_string(what);
           r.invoke();
           String __result;
           __result = r.return_value().extract_string();
           return __result;
   }
};

When a Java client gets a reference to a remote ThisOrThatServer object, it is given one of these stub objects. The client can make method calls on the stub object, and the stub converts these calls into corresponding requests to the ORB to invoke the methods on the remote object and send back the results.

The base class for the server implementation, shown in Example 4.6, accepts requests that are intended for the server implementation from the ORB. The base class converts a request into a method call on the server object and then takes the result of the call and gives it back to the ORB to send to the client stub. All this work is done in the server skeleton's invoke() method. The invoke() method figures out which method is being called, unpacks the method arguments (if any) from the request, and calls the method directly on itself.

Note that the server skeleton doesn't have implementations of the doThis() or doThat() methods declared in the interface. The idltojava compiler doesn't do everything for you; you still need to create a server implementation for your interface.

Example 4.6: Implementation Base Class for ThisOrThatServer


/*
 * File: ./_THISORTHATSERVERIMPLBASE.JAVA
 * From: THISORTHATSERVER.IDL
 * Date: Thu Apr 15 21:42:40 1999
 *   By: C:\JDK12~1.1\BIN\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

public abstract class _ThisOrThatServerImplBase extends 
    org.omg.CORBA.DynamicImplementation implements ThisOrThatServer {
    // Constructor
    public _ThisOrThatServerImplBase() {
         super();
    }
    // Type strings for this class and its superclasses
    private static final String _type_ids[] = {
        "IDL:ThisOrThatServer:1.0"
    };

    public String[] _ids() { return (String[]) _type_ids.clone(); }

    private static java.util.Dictionary _methods = new java.util.Hashtable();
    static {
      _methods.put("doThis", new java.lang.Integer(0));
      _methods.put("doThat", new java.lang.Integer(1));
     }
    // DSI Dispatch call
    public void invoke(org.omg.CORBA.ServerRequest r) {
       switch (((java.lang.Integer) _methods.get(r.op_name())).intValue()) {
           case 0: // ThisOrThatServer.doThis
              {
              org.omg.CORBA.NVList _list = _orb().create_list(0);
              org.omg.CORBA.Any _what = _orb().create_any();
              _what.type(org.omg.CORBA.ORB.init().get_primitive_tc(
                  org.omg.CORBA.TCKind.tk_string));
              _list.add_value("what", _what, org.omg.CORBA.ARG_IN.value);
              r.params(_list);
              String what;
              what = _what.extract_string();
              String ___result;
                            ___result = this.doThis(what);
              org.omg.CORBA.Any __result = _orb().create_any();
              __result.insert_string(___result);
              r.result(__result);
              }
              break;
           case 1: // ThisOrThatServer.doThat
              {
              org.omg.CORBA.NVList _list = _orb().create_list(0);
              org.omg.CORBA.Any _what = _orb().create_any();
              _what.type(org.omg.CORBA.ORB.init().get_primitive_tc(
                  org.omg.CORBA.TCKind.tk_string));
              _list.add_value("what", _what, org.omg.CORBA.ARG_IN.value);
              r.params(_list);
              String what;
              what = _what.extract_string();
              String ___result;
                            ___result = this.doThat(what);
              org.omg.CORBA.Any __result = _orb().create_any();
              __result.insert_string(___result);
              r.result(__result);
              }
              break;
            default:
              throw new org.omg.CORBA.BAD_OPERATION(0, 
                  org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE);
       }
 }
}

4.2.3 Writing the Implementation

So, we've written an IDL interface and generated the Java interface and support classes for it, including the client stub and the server skeleton. Now we need to create concrete server-side implementations of all of the methods on your interface. We do this by subclassing from the _xxxImplBase class generated by the idltojava compiler. For our example, we need to subclass _ThisOrThatServerImplBase and implement the doThis() and doThat() methods. The ThisOrThatServerImpl class in Example 4.7 does just that. Note that we've mimicked the method implementations from the RMI example in Chapter 3. The only real difference is that this ThisOrThatServerImpl class extends _ThisOrThatServerImplBase, while the one in Chapter 3 extends the UnicastRemoteObject.

Example 4.7: Server-Side Implementation of ThisOrThatServer Interface


public class ThisOrThatServerImpl extends _ThisOrThatServerImplBase {

  public ThisOrThatServerImpl() {}

  // Remotely-accessible methods
  public String doThis(String what) {
    return doSomething("this", what);
  }

  public String doThat(String what) {
    return doSomething("that", what);
  }

  // Non-remote methods
  private String doSomething(String todo, String what) {
    String result = todo + " " + what + " is done.";
    System.out.println("Did " + todo + " to " + what);
    return result;
  }
}

4.3 Putting It in the Public Eye

We still need to do some work to make the Java implementation of our IDL interface available to remote clients. There are two ways a client gets a reference to a remote object: it can get an initial object reference using the ORB.resolve_initial_references() method and somehow find a reference to the object through method calls on the initial object, or it can get a "stringified" reference to the remote object (an Interoperable Object Reference) and convert it locally to a live object reference.

For the first case, the remote object needs to be registered in some way with a server-side ORB. In order for you to register a remote object, you first have to get a reference to an ORB. We'll look at how to do that first, then look at registering the remote object with a Naming Service. We discuss the stringified object reference technique later in this chapter, when we look at how clients can access remote objects.

4.3.1 Initializing the ORB

Since the ORB is so central to everything in a CORBA environment, the first thing any CORBA process needs to do is get a reference to a local or remote ORB that it can use to find other objects, access CORBA services, and handle remote method calls. A CORBA participant initializes its ORB reference by calling one of the static init() methods on the ORB interface. Each of these methods returns an ORB object that can find CORBA objects and services. The standard init() methods provided on an ORB are as follows (Sun's Java IDL supports all of these standard initialization methods):

public static ORB ORB.init()

Returns a shared (static) ORB instance. Each call within the same runtime environment returns the same ORB reference. If used within an applet context, the ORB has limited abilities.

public static ORB ORB.init(String[] args, Properties props)

Creates a new ORB using the given arguments and properties, as discussed in the following paragraphs.

public static ORB ORB.init(Applet applet, Properties props)

Creates a new ORB within an applet context. The applet's codebase and host are used by the ORB as the source of various services, such as the Naming Service.

There are two standard properties defined for an ORB that can be set in the call to init(), using either the String arguments array or a Properties object. These are the ORBClass and ORBSingletonClass properties, which specify the Java classes to use to create ORBs when an init() method is called. (ORBSingletonClass is a shared ORB instance that is used mainly by generated classes to do things like create TypeCode objects that identify the types of CORBA objects, while ORBClass is a fully functional ORB.) You can use these properties to specify a custom ORB implementation. You may want to override the default ORB implementation (com.sun.CORBA.iiop.ORB in Java IDL) with one of your own that has particular performance characteristics. Or you may be running your CORBA code within an applet and want to ensure that a valid ORB is available no matter what browser version your applet encounters.

Sun's Java IDL also adds two nonstandard properties: ORBInitialHost and ORBInitialPort. By default, each ORB.init() method initializes an ORB that looks for its services locally. The current version of the Java IDL API includes a single service, the Naming Service, and the ORB.init() methods assume that the Naming Service is listening to port 900 on the local host. Java IDL adds these two nonstandard properties to allow your local ORB to defer its services (naming, trading, etc.) to a remote ORB running on a given host and listening on a given port. Be careful before you decide to depend on these properties in your application or applet. They are only honored within Sun's Java IDL implementation of the CORBA standard. If you want your CORBA application to be portable to any implementation of the standard IDL-to-Java binding, and you want to use a remote Naming Service, you should stick to using a stringified reference to the remote service, obtained through a secondary communication channel, as we'll discuss shortly.

Any of these properties can be specified within a Properties object or as a command-line option to a Java application. As an example, if you want to specify a different host to use for finding services like the Naming Service, one way to do this is to specify the host explicitly in the code that initializes the ORB, using a Properties object:


Properties orbProps = new Properties();
orbProps.put("org.omg.CORBA.ORBInitialHost", "remote.orb.com");
ORB myOrb = ORB.init((String[])null, orbProps); 

Alternately, you can take command-line arguments passed into your Java code and pass them to the ORB.init() method to be parsed. Say we have a class named InitRemote with a main method implemented as follows:


public class InitRemote {
  public static void main(String[] argv) {
    try {
      ORB myOrb = ORB.init(argv, null);
      ...
    }
  }
}

In this case, we can specify any ORB properties on the command line using specific argument names:


orbhost% java InitRemote -ORBInitialHost remote.orb.com

Note that you can use the second ORB.init() method with both a String arguments array and a Properties list specified, even though the examples here haven't shown that.

4.3.2 Registering with a Naming Service

One way to make a server object available to remote clients is to register it with the local CORBA Naming Service under a specific name. A remote client can then get a reference to the root NamingContext for the Naming Service and ask for the server object by name.

Example 4.8 shows a class whose main() method creates an instance of our ThisOrThatServer implementation and then registers the object with the Naming Service. The program starts by getting a reference to the local ORB. Then it asks the ORB for a reference to the Naming Service (using the standard name "NameService") with the resolve_initial_references() method. This reference is actually the root NamingContext, so we narrow the object reference using NamingContextHelper. We register the ThisOrThatServer with the Naming Service by building an array of NameComponent objects and then calling the rebind() method on the NamingContext. With the object registered, we go into a wait state, waiting for client requests.

Example 4.8: Registering an Object with the Naming Service


import org.omg.CORBA.*;
import org.omg.CosNaming.*;

public class ServerNamingInit {
  public static void main(String[] argv) {
    try {
      // Obtain ORB reference
      ORB myORB = ORB.init(argv, null); 

      // Make a ThisOrThatServer object to register with the ORB
      ThisOrThatServer impl = new ThisOrThatServerImpl();

      // Get the root name context
      org.omg.CORBA.Object objRef = 
        myORB.resolve_initial_references("NameService");
      NamingContext nc = NamingContextHelper.narrow(objRef);

      // Register the local object with the Name Service
      NameComponent ncomp = new NameComponent("ThisOrThatServer", "");
      NameComponent[] name = {ncomp};
      nc.rebind(name, impl);

      // Go into a wait state, waiting for clients to connect
      System.out.println("Waiting for clients...");
      java.lang.Object dummy = new String("I wait...");
      synchronized (dummy) {
        dummy.wait();
      }
    }
    catch (Exception e) {
      System.out.println("Error occurred while initializing server object:");
      e.printStackTrace();
    }
  }
}

Note that Example 4-8 imports the org.omg.CORBA and org.omg.CosNaming packages, which are the two main packages in Java IDL. Because of the naming collision between java.lang.Object and org.omg.CORBA.Object, we have to use the fully qualified names of these two classes when we use them in CORBA applications that import org.orm.CORBA. Finally, note that this example binds the ThisOrThatServer object within the root NamingContext using the name "ThisOrThatServer". We'll see shortly how to create subcontexts and bind objects within them.

Before running this initialization of our CORBA object, we need to start a Naming Service on the host for the object. A Naming Service daemon listens for Naming Service requests on a specific port and provides access to the named object directory it manages. In Java IDL, the Naming Service is started using the tnameserv command:


objhost% tnameserv &

With that done, we can run our initialization method to register our server object with the ORB:


objhost% java ServerNamingInit

4.3.3 Adding Objects to a Naming Context

Initially, a CORBA naming directory is empty, with only its root NamingContext and no objects. The bind() method on a NamingContext object binds a server object to a name within the context. The bind_new_context() method creates new subcontexts within a given NamingContext. Using a file directory analogy, calling bind_new_context() on a NamingContext object is like making a new subdirectory, while calling bind() puts a new file into a directory.

The Java IDL mapping uses arrays of NameComponent objects to represent the names of subcontexts within a naming directory. Each NameComponent represents a component of the path to the named object. A NameComponent contains id and kind string fields that serve to label the component in the path. Only the id field is significant in determining name uniqueness. So a NameComponent with id set to "student" and kind set to an empty string conflicts with a NameComponent with an id of "student" and kind "doctoral," if both NameComponent objects are relative to the same subcontext. The NameComponent class has a constructor that takes the id and kind values as arguments. Here's how to create a single NameComponent:


NameComponent comp1 = new NameComponent("student", "doctoral");

A complete name path can be composed as an array of these objects:


NameComponent path[] = { comp1, comp2, ... };

The bind() method takes two arguments: an array of NameComponent objects as the relative name for the object you're putting into the Naming Service and the server object itself. If you're binding a server object using the root context of the Naming Service, the name is also the absolute name of the object in the overall naming directory. If an object is already bound to the name, you can use the rebind() method with the same arguments, causing the existing object bound to that name to be replaced by the new object. Note that since the Naming Service is a CORBA service that can be accessed remotely by other CORBA clients, the objects it contains need to be exportable to these remote clients. This means that only org.omg.CORBA.Object references can be bound to names within a NamingContext.

The following code binds a few of our ThisOrThatServer objects to names within the root context of a Naming Service:


// Get the root naming context
ORB myORB = ORB.init(...);
org.omg.CORBA.Object ref = 	myORB.resolve_initial_references("NameService");
NamingContext rootNC = NamingContextHelper.narrow(ref);

// Create a few servers
org.omg.CORBA.Object ref1 = new ThisOrThatServerImpl();
org.omg.CORBA.Object ref2 = new ThisOrThatServerImpl();

// Bind them to names in the Naming Service
NameComponent name1 = new NameComponent("server1", "");
NameComponent path1[] = { name1 };
NameComponent name2 = new NameComponent("server2", "");
NameComponent path2[] = { name2 };
rootNC.bind(path1, ref1);
rootNC.bind(path2, ref2);

Before you can bind an object to a name with multiple components, all the subcontexts (subdirectories) have to be created using the bind_new_context() method on a NamingContext. The bind_new_context() method takes an array of NameComponent objects as the relative path of the new context and a reference to the NamingContext object to bind to that location in the overall directory. A new NamingContext object can be created from an existing one by calling its new_context() method. If a context already exists at the target name, you can use the rebind_context() method to replace the existing context with a new one. This is useful for emptying out an entire subcontext without removing each object individually.

Here is an example that binds some objects within various subcontexts:


// Get the root context, as before
NamingContext rootNC = ...;

// Create the components to the subcontext name
NameComponent comp1 = new NameComponent("servers", "");
NameComponent ttComp = new NameComponent("ThisOrThat", "");
NameComponent otherComp= new NameComponent("misc", "");

// Create each subcontext within the root context and bind them
// to their appropriate names
// Create a new context, bind it to the name "servers"
// off the root NamingContext
NamingContext context1 = rootNC.new_context();
NameComponent path1[] = { comp1 };
rootNC.bind_context(path1, context1);
// Create another context, bind it to the name "servers, ThisOrThat"
NamingContext ttDir = rootNC.new_context();
NameComponent path2_1[] = { comp1, ttComp };
rootNC.bind_context(path2_1, ttDir);
// Create another context, bind it to the name "servers, misc"
NamingContext otherDir = rootNC.new_context();
NameComponent path2_2[] = { comp1, otherComp };
rootNC.bind_context(path2_2, otherDir);

// Now we can bind servers to a name within any of the new subcontexts
org.omg.CORBA.Object ttRef = new ThisOrThatServerImpl();
org.omg.CORBA.Object otherRef = new SomeOtherServerImpl();
// Bind the other server to the "misc" branch of the "servers" dir.
NameComponent yetAnotherComp = new NameComponent("SomeOtherServer", "");
NameComponent otherPath[] = { comp1, otherComp, yetAnotherComp };
rootNC.bind(otherPath, otherRef);

// Bind the ThisOrThatServer to the appropriate branch under "servers"
NameComponent tt1Comp = new NameComponent("server1", "");
NameComponent ttPath[] = { comp1, ttComp, tt1Comp };
rootNC.bind(ttPath, ttRef);

If you try to bind an object or a subcontext to a name within a context that hasn't been created yet, a org.omg.CosNaming.NamingContextPackage.NotFound exception is thrown.

Note that names used in the bind() or rebind() methods are relative to the NamingContext object that they're called on. This means we can bind our ThisOrThatServer object in the previous example to the same absolute name within the directory by replacing the last two lines of the example with the following:


NameComponent relObjPath[] = { tt1Comp };
ttDir.bind(relObjPath, ttRef);

The ttDir context is bound to the {"servers", "ThisOrThat"} subdirectory, so binding an object to the name {"server1"} within this context is equivalent to binding it to the full path {"servers", "ThisOrThat", "server1"} from the root context. You can use similar shorthand when binding new contexts within a directory. In other words, you can bind a context to a relative name within a subcontext, instead of an absolute name within the root context.

4.4 Finding Remote Objects

Now that we have registered our remote object with an ORB, it is available to CORBA client applications. This means we are done with the setup of the remote object and can turn our attention to client applications that want to use the object. As I said earlier, every CORBA process needs a reference to an ORB. Once a client application has access to an ORB, the next thing for it to do is find remote objects to interact with. But before we can discuss finding remote objects, we need to talk a bit about what remote object references look like under CORBA.

The whole point of CORBA is to be able to distribute objects across the network and then use them from any point on the network. In order for a local process to make requests of a remote object, it needs to have some kind of reference to that remote object. This object reference needs to contain enough information for the local ORB to find the ORB serving the target object and send the request to the remote ORB using an agreed-upon protocol.

In most situations, a CORBA client has a reference to a remote object in the form of an object stub. The stub encapsulates the actual object reference, providing what seems like a direct interface to the remote object in the local environment. If the client is implemented in C++, Java, or some other object-oriented language, the object stub is a native object in that language. Other, nonobject languages represent remote object references in whatever way is dictated in the CORBA language binding for that language.

CORBA includes its own root object class, since some object programming languages may have different inheritance structures. In the Java binding for CORBA, all CORBA object references (local or remote) implement the org.omg.CORBA.Object interface. So, when a client of a remote CORBA object receives a stub for the object, it actually gets an org.omg.CORBA.Object that serves as a proxy for the remote object. The org.omg.CORBA.portable.ObjectImpl class provides default implementations for the methods defined on org.omg.CORBA.Object. Java stubs and implementations for CORBA objects are actually subclassed from the ObjectImpl class. Internally, ObjectImpl deals with delegating requests on the object to the proper target object, whether it is a remote object or a local one. ObjectImpl implements the org.omg.CORBA.Object interface and extends the java.lang.Object class, so it truly provides a joining point between the CORBA and Java object environments.

A reference to an org.omg.CORBA.Object object that is connected to a remote object is all a client needs to invoke methods on a remote object. Using the Dynamic Invocation Interface defined by the CORBA standard, you can create method requests and send them to the remote object through the Object interface, as we'll discuss later in this chapter. If your client has the actual Java interface for the remote object available at compile time, however, you probably want to convert the Object reference into a reference of that type, so that you can use the interface to call remote methods directly.

Converting an org.omg.COBRA.Object to a specific remote interface is done by narrowing the object reference to the corresponding interface type, using type-specific helper classes to do the narrowing. We've already seen how the Java IDL compiler, idltojava, creates a helper class from an IDL interface (e.g., ThisOrThatServerHelper). The helper class includes a narrow() method that converts an org.omg.CORBA.Object reference to a reference of the given type. If the object reference you pass into the narrow() method is not the type the helper expects, an org.omg.CORBA.BAD_PARAM exception is thrown. This is a RuntimeException, so it doesn't have to be caught by your code, unless you're trying to test the type of a CORBA reference for some reason.

With that background material out of the way, let's discuss actually finding remote object references. There are many ways that an object reference can find its way through the ORB into a client application, but they all boil down to one of these methods:

4.4.1 Initial ORB References

In addition to providing core object communication services, an ORB can also provide additional services, such as a Naming Service, a Trading Service, a Security Service, etc. These services are represented as CORBA objects and are available through the ORB automatically, based on how it is configured. The ORB interface provides the resolve_initial_references() method for obtaining references to these initial objects. Each CORBA service the ORB supports is represented by one or more object interfaces, and these objects can be asked for using standard names. As we saw earlier when we registered a remote object, the standard name for the Naming Service is "NameService."

Once you've initialized your ORB reference, you can ask the ORB for a list of the names of its initial objects using the list_initial_references() method:


String names[] = myORB.list_initial_references();

This method returns an array of String objects that contains the names of all initial objects in the ORB. These names can then be used to get references to the objects through the resolve_initial_references() method.

Here's how we used resolve_initial_references() to obtain a reference to the Naming Service in Example 4-8:


ORB myORB = ORB.init(...);
org.omg.CORBA.Object nameRef = 
	myORB.resolve_initial_references("NameService");

Although the list_initial_references() and resolve_initial_references() methods are a standard element of the ORB interface, how the ORB implements these initial object references is not standardized. Sun's Java IDL implementation stores an ORB's initial object references as root objects in its Naming Service.

4.4.2 Getting Objects from Other Remote Objects

In addition to getting remote objects directly from an ORB reference, a client can obtain remote objects from other remote objects. A common variation on this approach is to get a reference to a Naming Service object and then look up objects in the naming directory by name. Another variation (that we won't cover in detail in this section) is to obtain an application-specific object reference, either directly from the ORB or through the Naming Service, and use this initial reference to request other objects. An object used in this way in a distributed application is sometimes called a factory object.

4.4.2.1 Using a naming context

Once you have a reference to a Naming Service that you can narrow to a NamingContext reference, you can look up objects within the context by passing names to its resolve() method. As before, when we were binding objects, a name is represented by an ordered array of NameComponent objects. Each NameComponent (both the id field and the kind field) must exactly match the path to an object within the context in order to successfully find the object. If an object is not found at a specified name, an org.omg.CosNaming.NamingContextPackage.NotFound exception is thrown.

So, if a client wants to find the object we stored in the last binding example, it needs to do the following (assuming that it already has a reference to the root naming context of the Naming Service):

// Set up path
NameComponent comp1 = new NameComponent("servers", "");
NameComponent comp2 = new NameComponent("ThisOrThat", "");
NameComponent serverName = new NameComponent("server1", "");
NameComponent objPath[] = { comp1, comp2, serverName };

// Find the object in the directory
org.omg.CORBA.Object objRef = rootNC.resolve(objPath);
ThisOrThatServer server = ThisOrThatServerHelper.narrow(objRef);

Note the use of the narrow() method on ThisOrThatServerHelper to "cast" the generic object reference to a ThisOrThatServer object.

You can also use the resolve() method on a NamingContext to get a reference to a subcontext. Just use the path to the context itself and narrow() it to a NamingContext reference:


NameComponent ttPath[] = { comp1, comp2 };
org.omg.CORBA.Object ncRef = rootNC.resolve(ttPath);
NamingContext ttContext = NamingContextHelper.narrow(ncRef);

4.4.2.2 Using multiple naming services

Suppose there are objects stored in multiple Naming Services (representing, for example, multiple organizations offering CORBA-based services) that you want to access from your client. One way to do this is to initialize an ORB reference for each one. Sun's Java IDL lets you specify an initial host and port for an ORB when you initialize it. So, if each independent Naming Service has its own ORB behind it, you can simply get a reference to each ORB and ask it for a reference to its Naming Service:


String host1 = "orbhost1.net";
int port1 = 1234;
String host2 = "orghost2.net";
int port2 = 2345;

// Initialize the first ORB reference
Properties props = new Properties();
props.put("org.omg.CORBA.ORBInitialHost", host1);
props.put("org.omg.CORBA.ORBInitialPort", String.valueOf(port1));
ORB orb1 = ORB.init((String[])null, props);

// Initialize another ORB reference
props.put("org.omg.CORBA.ORBInitialHost", host2);
props.put("org.omg.CORBA.ORBInitialPort", String.valueOf(port2));
ORB orb2 = ORB.init((String[])null, props);

// Get references to the Naming Services
org.omg.CORBA.Object nc1Ref = 
	orb1.resolve_initial_references("NameService");
org.omg.CORBA.Object nc2Ref = 
	orb2.resolve_initial_references("NameService");

// Narrow the Naming Service references to NamingContexts and use them
...

The only problem with this approach is that it depends on using a nonstandard feature of Sun's Java implementation of the CORBA standard. If you try using this same code against a different Java implementation of CORBA, it probably won't work.

Another option is to have one Naming Service hold references to other Naming Services located elsewhere on the network. As we've seen, the interface to a Naming Service is a NamingContext object reference that represents the root of the naming tree for that name directory. Since the NamingContext is itself a CORBA-exported object, one Naming Service can hold a reference to a NamingContext from another Naming Service, acting as a bridge to the other Naming Service and its objects. To do this, you first have to run some code on the server that is going to act as the bridge. This code gets a reference to the local Naming Service and stores references to remote Naming Services in the local directory:


// Get the local ORB and main NamingContext
ORB myORB = ORB.init(...);
org.omg.CORBA.Object ncRef = 
  orb.resolve_initial_references("NameService");
NamingContext localNC = NamingContextHelper.narrow(ncRef);

// Create a new subcontext to hold the remote contexts
NameComponent nodeName = new NameComponent("RemoteContexts", "");
NameComponent path[] = {nodeName};
NamingContext ncNode = localNC.bind_new_context(path);

// Get a reference to a remote Naming Service
// using Sun's non-standard ORB properties
Properties remoteORBProps = new Properties();
remoteORBProps.put("org.omg.CORBA.ORBInitialHost", "remote.orb.com");
ORB remoteORB = ORB.init((String[])null, remoteORBProps);
org.omg.CORBA.Object remoteNCRef = 
  remoteORB.resolve_initial_references("NameService");
NamingContext remoteNC = NamingContextHelper.narrow(remoteNCRef);

// Store the remote reference in the local context
NameComponent sub = new NameComponent("Naming1", "");
NameComponent path2[] = {nodeName, sub};
localNC.bind(path2, remoteNC);

With this done, a remote client can get a reference to the main Naming Service directory and then look up other remote directories within the bridge directory:


public class NamingClient {
  public static void main(String argv[]) {
    ORB orb = ORB.init(argv, null);
    org.omg.CORBA.Object ref = null;
    try {
      ref = orb.resolve_initial_references("NameService");
    }
    catch (InvalidName invN) {
      System.out.println("No primary NameService available.");
      System.exit(1);
    }
    NamingContext nameContext = NamingContextHelper.narrow(ref);
    NameComponent topNC = new NameComponent("RemoteContexts", "");
    NameComponent subNC = new NameComponent("Naming1", "");
    NameComponent path[] = {topNC, subNC };
    try {
      org.omg.CORBA.Object ref2 = nameContext.resolve(path);
      NamingContext nameContext2 = NamingContextHelper.narrow(ref2);
      System.out.println("Got secondary naming context...");
    }
    catch (Exception e) {
      System.out.println("Failed to resolve secondary NameService:");
      e.printStackTrace();
    }
  }
}

Using one Naming Service as a bridge to other remote named object directories is a useful tool to help manage a constellation of remote objects, but the same question arises: how do we get references to the remote NamingContext objects in order to store them in the bridge directory? In the previous bridge example, we're still using the nonstandard ORB properties provided by Sun's Java IDL implementation to initialize references to multiple remote ORBs (and their Naming Services). What we really want to do is initialize the bridge directory in a way that falls within the CORBA standard. One way is to do this is to use stringified object references, which are the topic of the next section.

4.4.3 Stringified Object References

As we've seen, Sun's implementation of Java IDL provides a nonstandard way to initialize an ORB to reference a remote Naming Service, so that one of the ORB's initial references is to the root context of the remote Naming Service. But what do you do if you want an object from a remote Naming Service, and your Java IDL implementation doesn't provide a way to directly initialize a reference to the remote service? Or, worse yet, what if the object that you want isn't stored in a Naming Service or available through any other CORBA service? How can your client get a reference to the object?

The CORBA standard comes to the rescue again. Part of the standard, called Interoperable Object References (IORs), includes a syntax for representing a remote object reference in the form of a printable string of characters. This stringified object reference includes enough information for a remote CORBA client to locate the object's home ORB and convert the string to a runtime stub reference to the object. Two methods on the ORB interface, object_to_string() and string_to_object(), let you convert a CORBA object reference to string form and back again.

Example 4.9 shows how to create an instance of our server implementation of the ThisOrThatServer interface, register it with the ORB, and generate a stringified object reference from the CORBA server object. A stringified reference to a remote object is called an Interoperable Object Reference (IOR) because it uses a format for object references that can be freely distributed between ORBs running a cross the network. In order for the IOR you generate to be acceptable to another ORB, both your ORB and the remote ORB have to be using the same inter-ORB communication protocol (IIOP, DCE-CIOP, etc.). In this example, our client and host are both running IIOP.

Example 4.9: Registering an Object/Getting Its Stringified Object Reference


import org.omg.CORBA.*;

public class ServerInit {
  public static void main(String[] argv) {
    try {
      // Obtain ORB reference
      ORB myORB = ORB.init(argv, null); 

      // Make a ThisOrThatServer object to register with the ORB
      ThisOrThatServer impl = new ThisOrThatServerImpl();

      // Register the local object with the ORB
      myORB.connect(impl);

      // Get a stringified reference to the object
      String sor = myORB.object_to_string(impl);
      System.out.println("ThisOrThatServer IOR: " + sor);

      // Go into a wait state, waiting for clients to connect
      java.lang.Object dummy = new String("I wait...");
      synchronized (dummy) {
        dummy.wait();
      }
    }
    catch (Exception e) {
      System.out.println("Error occurred while initializing server object:");
      e.printStackTrace();
    }
  }
}

The ServerInit class contains a main() method that is intended to be run on the server host for our remote object. The main() method first initializes a connection to the local ORB and then creates an instance of the ThisOrThatServerImpl class. This instance serves as the server implementation of our remote object. We create a stringified reference to the object using the object_to_string() method on the ORB and then output the stringified reference, so that it can be copied and sent to clients. Finally, by doing a synchronous wait() on a local object, the main() method goes into a wait state. This wait() is necessary to keep the ORB running so that it can respond to client requests. If we let the main() method exit, the server object we created is destroyed, and the IOR we generated is no longer valid.

A sample client for our object is shown in Example 4.10. The client accepts a stringified object reference as a command-line argument to its main() method. Then it initializes a local ORB reference and uses its string_to_object() method to convert the stringified reference to a live object reference. To do this, the ORB parses the encoded information in the stringified reference, makes a connection with the remote ORB serving the object, and generates a CORBA object reference for the client.

Example 4.10: A Client Utilizing a Stringified Object Reference


import org.omg.CORBA.*;

public class ServerStringClient {
  public static void main(String[] argv) {
    // Get the stringified reference from our command-line arguments
    String sor = null;
    if (argv.length > 0) {
      sor = argv[0];
    }
    else {
      System.out.println("You forgot the object reference...");
      System.exit(1);
    }

    try {
      // Obtain ORB reference
      ORB myORB = ORB.init(argv, null); 

      // Convert the stringified reference into a live object reference
      org.omg.CORBA.Object objRef = myORB.string_to_object(sor);

      // Narrow the object reference to a ThisOrThatServer
      // using the ThisOrThatServerHelper
      ThisOrThatServer server = ThisOrThatServerHelper.narrow(objRef);

      // Invoke some methods on the remote object through the stub
      server.doThis("something");
      server.doThat("something else");
    }
    catch (Exception e) {
      System.out.println("Error occurred while initializing server object:");
      e.printStackTrace();
    }
  }
}

Before we can run the client, the remote object has to be registered with its ORB, so that we can get the stringified object reference:

objhost% java ServerInit
ThisOrThatServer IOR: IOR:000000000000002349444c3a6a656e2f636f7262612f546869
734f72546861745365727665723a312e30000000000001000000000000003000010000000000
0a6c6f63616c686f737400043200000018afabcafe00000002496bb469000000080000000000
000000

Somehow, you have to get this IOR to the client host. You could embed the stringified object reference within a hidden field in a HTML page, so that a Java client can access it using a URL object. Or you could set up a simple server on a given port on your host that broadcasts the stringified object reference to whoever makes a socket connection. Or you could email the string to a colleague, and she can type the stringified reference into the startup command for her CORBA client. In any case, the client is invoked with the IOR as a command-line option:

clienthost% java ServerStringClient IOR:000000000000002349444c3a6a656e2f636f
7262612f546869734f72546861745365727665723a312e300000000000010000000000000030
000100000000000a6c6f63616c686f737400043200000018afabcafe00000002496bb4690000
00080000000000000000

The client uses the argument to reconstitute a remote reference to the server object, so that it can invoke methods on the remote object.

4.5 What If I Don't Know the Interface?

In the examples we've seen so far, we've always assumed that the Java interfaces for the remote objects are available at compile time. But what happens if they aren't? You might get a reference to a CORBA Object from a Naming Service, for example, and not know what interface that object implements. I mentioned earlier that you can use an org.omg.CORBA.Object reference directly to make requests and exchange data with its remote object.

The CORBA standard defines two complementary APIs for this purpose: the Dynamic Invocation Interface (DII) that a client can use to make remote method requests of a server object, and the Dynamic Skeleton Interface (DSI) that a server-side skeleton can use to forward method invocations to its server implementation object. Both of these APIs provide the same essential function: a dynamic interface to an object whose interface is not known at compile time. The DII offers this functionality to clients of CORBA objects, and the DSI provides it to the server-side skeletons that bridge the object implementation with the ORB.

The DII and DSI may seem like sidebar topics in the CORBA world, but in reality they are at the heart of CORBA and how it works. When we generate Java stubs and skeletons from IDL interfaces, the code that is generated uses the DII and DSI to execute remote method calls. The details of how this is done are shielded from you, the developer, by the Java interface you use to interact with the remote object. But it's still worthwhile to understand how CORBA objects implement their distributed nature, especially in situations where the Java interface for the remote object is not there, and you need to deal directly with these details.

In this section, we take a look at how the DII works and how you might use it in a client. We won't cover the DSI in this book, since its practical uses are even more limited for the average developer. Note, however, that the API of the DSI is analogous to that of the DII, so you shouldn't have much trouble mapping the following explanation to the DSI as well.

4.5.1 Dynamic Invocation Interface

The Dynamic Invocation Interface provides abstract representations of remote method requests and their arguments. In simple terms, this means it includes objects that represent remote method requests and parameters that are passed with these method requests. Methods on these objects allow you to set the parameters to the request, make the request, and get the results. DII's central classes are:

Request

A request to invoke a method on a remote object. Created by the client and issued through the ORB to the server object.

NamedValue

A named parameter to a method request. Conceptually, this is a name tied to an Any value. The name of the value must match the name of the parameter as specified in the IDL interface the remote object satisfies.

NVList

A list of NamedValue parameters used to represent an argument list passed into a remote method request.

Any

A general argument value. An Any object can contain the Java equivalent of any basic IDL type or an Object that can be described in IDL.

Context

A list of NamedValue objects used to specify any details of the client environment that shouldn't be passed as method arguments.

Once you get an org.omg.CORBA.Object reference to a remote object (using any of the approaches we've already covered), you can create and issue a method request to the object by building a parameter list for the method call, making a NamedValue object to hold the result, making a Context object and putting any useful environment values in it, and then using all of these items to create a Request object that corresponds to a particular method on the object. Example 4.11 shows a sample DII client that gets a reference to a remote object through a Naming Service and then makes a dynamic call to its doThis() method.

Example 4.11: Client Using DII to Make Remote Method Call


import org.omg.CORBA.*;
import org.omg.CosNaming.*;

public class DIISimpleClient {
  public static void main(String argv[]) {
    ORB myORB = ORB.init(argv, null);
    ORB singleORB = ORB.init();
    try {
      // Get a reference to the object
      org.omg.CORBA.Object ncRef = 
        myORB.resolve_initial_references("NameService");
      NamingContext nc = NamingContextHelper.narrow(ncRef);
      NameComponent nComp = new NameComponent("ThisOrThatServer", "");
      NameComponent[] path = {nComp};
      org.omg.CORBA.Object objRef = nc.resolve(path);

      // Now make a dynamic call to the doThis method.  The first step is
      // to build the argument list. In this case, there's a single String
      // argument to the method, so create an NVList of length 1.  Next
      // create an Any object to hold the value of the argument and insert
      // the desired value.  Finally, wrap the Any object with a NamedValue
      // and insert it into the NVList, specifying that it is an input
      // parameter.
      NVList argList = myORB.create_list(1);
      Any arg1 = myORB.create_any();
      arg1.insert_string("something");
      NamedValue nvArg = 
        argList.add_value("what", arg1, org.omg.CORBA.ARG_IN.value);

      // Create an Any object to hold the return value of the method and
      // wrap it in a NamedValue
      Any result = myORB.create_any();
      result.insert_string("dummy");
      NamedValue resultVal = myORB.create_named_value("result", result, 
          org.omg.CORBA.ARG_OUT.value);

      // Get the local context from the ORB.  
      // NOTE: This call does not work in Java 1.2, and returns a
      //   NOT_IMPLEMENTED exception.  To make this work in Java 1.2, simply
      //   remove this call to get_default_context(), and pass a null pointer
      //   into the _create_request() call below.  This example should work 
      //   as is with any compliant Java CORBA environment, however.
      Context ctx = myORB.get_default_context();

      // Create the method request using the default context, the name of
      // the method, the NVList argument list, and the NamedValue for the 
      // result.  Then invoke the method by calling invoke() on the Request.
      Request thisReq = 
        objRef._create_request(ctx, "doThis", argList, resultVal);
      thisReq.invoke();

      // Get the return value from the Request object and output results.
      result = thisReq.result().value();
      System.out.println("doThis() returned: " + result.extract_string());
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Note that in most situations you will have the Java interface for the remote object available in your client along with its helper class, so you'll be able to narrow the Object reference to a specific type. One exception might be if you're building some kind of software development tool, and you want to provide a dynamic execution utility for the CORBA code being developed. The previous example demonstrates how a CORBA method call can be carried out at this lower level, in case you ever find it necessary to do so. And when you're trying to fix a problem with your CORBA application, it's always better to understand what's going on under the hood, so to speak.

Back to: Java Enterprise in a Nutshell


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.