|
|
|
|
Java Enterprise in a NutshellA Desktop Quick ReferenceBy David Flanagan, Jim Farley, William Crawford & Kris Magnusson1st 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.CORBAandorg.omg.CosNamingpackages 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
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
![]()
Each branch in the directory tree is called a naming context, and leaf objects have bindings to specific names. The
org.omg.CosNaming.NamingContextinterface represents each branch in the naming directory. EachNamingContextcan 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 theresolve_initial_references()method. The standard name for the Naming Service is "NameService", so the following code snippet gets the rootNamingContext: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
Objectreference to aNamingContextreference using theNamingContextHelper.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 theresolve_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 basicObjectImplobject 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 appropriatenarrow()method. In the Java mapping of IDL, this means calling thenarrow()method on the corresponding helper class. Thenarrow()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:
Writing an interface in the CORBA Interface Definition Language
Generating a Java base interface, plus a Java stub and skeleton class, using an IDL-to-Java compiler
Writing a server-side implementation of the Java interface in Java
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:
IDL is a declaration language. In IDL, you declare only the names and types for interfaces, data members, methods, method parameters, etc. Method implementations are created in the implementation language you choose (in this case Java), after you've used an IDL compiler to convert your IDL interface to your target language.
IDL, like C++, includes non-class data structure definitions, like structs, unions, and enumerations.
Method parameters in IDL include modifiers that specify whether they are input, output, or input/output variables. In Java, all primitive data types are passed by value, and all object data types are passed by reference.
An IDL file can include multiple public interfaces. Java allows multiple inner classes within a single public class definition and multiple nonpublic classes per file, but only a single public class can be defined in a given Java file.
Modules, which are similar to Java packages, can be nested within other modules in the same IDL file, and interfaces in multiple distinct modules can be defined in the same IDL file. In Java, you can define a class only within a single package in a single Java file.
4.2.1.1 Modules
Modules are declared in IDL using the
modulekeyword, 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 syntaxmodulename::x. Suppose that you want all your classes to be contained in a module calledcorba, which is part of a larger module calledjen(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
NeatExampleinterface in other IDL files, use the syntaxjen::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
PrintServerthat inherits all the methods and data members from theServerinterface. 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
attributekeyword. 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 thereadonlykeyword. 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-onlystringattribute: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 singlestringargument and returns astringvalue.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-onlystringattribute and aninit()method that accepts astringand returns aboolean. ThePrintableinterface has a singleprint()method that accepts a string header. Finally, thePrintServerinterface extends theServerinterface (hence inheriting all its methods and attributes) and adds aprintThis()method that accepts aPrintableobject and returns aboolean. In all cases, we've declared our method arguments as input-only (i.e., pass-by-value), using theinkeyword.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:
A Java interface with the same name as the IDL interface. This can act as the basis for a Java implementation of the interface (but you have to write it, since IDL doesn't provide any details about method implementations).
A helper class whose name is the name of the IDL interface with "Helper" appended to it (e.g.,
ServerHelper). The primary purpose of this class is to provide a staticnarrow()method that can safely cast CORBAObjectreference s to the Java interface type. The helper class also provides other useful static methods, such asread()andwrite()methods that allow you to read and write an object of the corresponding type using I/O streams.A holder class whose name is the name of the IDL interface with "Holder" appended to it (e.g.,
ServerHolder). This class is used when objects with this interface are used asoutorinoutarguments in remote CORBA methods. Instead of being passed directly into the remote method, the object is wrapped with its holder before being passed. When a remote method has parameters that are declared asoutorinout, the method has to be able to update the argument it is passed and return the updated value. The only way to guarantee this, even for primitive Java data types, is to forceoutandinoutarguments to be wrapped in Java holder classes, which are filled with the output value of the argument when the method returns.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
.
A client stub class, called
_interface-nameStub, that acts as a client-side implementation of the interface and knows how to convert method requests into ORB requests that are forwarded to the actual remote object. The stub class for an interface namedServeris called_ServerStub.A server skeleton class, called
_interface-nameImplBase, that is a base class for a server-side implementation of the interface. The base class can accept requests for the object from the ORB and channel return values back through the ORB to the remote client. The skeleton class for an interface namedServeris called_ServerImplBase.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()anddoThat(). 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 isstring, and the parameters are declared asinarguments, 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.idlThis 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-cppoption 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.Objectinterface. If we had included any module definitions in our IDL specification, they would have been mapped into apackagestatement at the beginning of the Java file. The IDLstringtype is converted into the JavaStringtype, and, since they don't require any special handling in a remote method call, theinmethod 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 writeThisOrThatServerobjects to and from CORBA I/O streams, get theTypeCodefor aThisOrThatServerobject, and, most importantly, safely narrow a CORBAObjectreference into aThisOrThatServerreference.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 CORBAObjectreference to a reference to a specific type. First, thenarrow()method checks to see if theObjectparameter is already aThisOrThatServerobject (using the Javainstanceofoperator), then it checks to see if the object passed in is anullpointer. If neither case is true, theObjectshould contain a delegate of aThisOrThatServerobject. Every CORBA stub for a remote object contains an internalDelegateobject (from theorg.omg.CORBA.portablepackage) that's used by the stub to invoke remote requests. If the object's delegate is aThisOrThatServer(checked using the objects's_is_a()method), the delegate is used to create a newThisOrThatServerstub. We'll take a look at theThisOrThatServerstub class in a bit. If the object doesn't contain a delegate, theis_a()method returnsfalse, and thenarrow()method throws aBAD_PARAMexception.4.2.2.3 The holder class
The compiler generates a holder class for the
ThisOrThatServerclass, as shown in Example 4.4. The holder class, calledThisOrThatServerHolder, is a wrapper used whenThisOrThatServerobjects are called for asoutorinoutarguments in an IDL method. All holder classes implement theStreamableinterface from theorg.omg.CORBA.portablepackage. An ORB knows to passStreamableobjects in method calls using the_read()and_write()methods of theStreamableobject; 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 aninoutargument, 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
ThisOrThatServeras aninoutparameter:// IDL interface ServerManager { boolean updateServer(inout ThisOrThatServer server); };The Java interface generated from this IDL interface uses the holder class for the
ThisOrThatServeras the type for the corresponding Java method parameter:// Java public interface ServerManager extends org.omg.CORBA.Object { boolean updateServer(ThisOrThatServerHolder server) ; }The
ThisOrThatServerHolderclass has public constructors that let you create a holder from an existingThisOrThatServerobject, 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 generatedThisOrThatServerJava interface and acts as a client-side proxy for a remoteThisOrThatServerobject. The stub has implementations of thedoThis()anddoThat()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
ThisOrThatServerobject, 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. Theinvoke()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()ordoThat()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
_xxxImplBaseclass generated by the idltojava compiler. For our example, we need to subclass_ThisOrThatServerImplBaseand implement thedoThis()anddoThat()methods. TheThisOrThatServerImplclass 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 thisThisOrThatServerImplclass extends_ThisOrThatServerImplBase, while the one in Chapter 3 extends theUnicastRemoteObject.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 anORBobject that can find CORBA objects and services. The standardinit()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 theStringarguments array or aPropertiesobject. These are theORBClassandORBSingletonClassproperties, which specify the Java classes to use to create ORBs when aninit()method is called. (ORBSingletonClassis a shared ORB instance that is used mainly by generated classes to do things like createTypeCodeobjects that identify the types of CORBA objects, whileORBClassis 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.ORBin 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:
ORBInitialHostandORBInitialPort. By default, eachORB.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 theORB.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
Propertiesobject 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 aPropertiesobject: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 namedInitRemotewith 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.comNote that you can use the second
ORB.init()method with both aStringarguments array and aPropertieslist 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
NamingContextfor the Naming Service and ask for the server object by name.Example 4.8 shows a class whose
main()method creates an instance of ourThisOrThatServerimplementation 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 theresolve_initial_references()method. This reference is actually the rootNamingContext, so we narrow the object reference usingNamingContextHelper. We register theThisOrThatServerwith the Naming Service by building an array ofNameComponentobjects and then calling therebind()method on theNamingContext. 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.CORBAandorg.omg.CosNamingpackages, which are the two main packages in Java IDL. Because of the naming collision betweenjava.lang.Objectandorg.omg.CORBA.Object, we have to use the fully qualified names of these two classes when we use them in CORBA applications that importorg.orm.CORBA. Finally, note that this example binds theThisOrThatServerobject within the rootNamingContextusing 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 ServerNamingInit4.3.3 Adding Objects to a Naming Context
Initially, a CORBA naming directory is empty, with only its root
NamingContextand no objects. Thebind()method on aNamingContextobject binds a server object to a name within the context. Thebind_new_context()method creates new subcontexts within a givenNamingContext. Using a file directory analogy, callingbind_new_context()on aNamingContextobject is like making a new subdirectory, while callingbind()puts a new file into a directory.The Java IDL mapping uses arrays of
NameComponentobjects to represent the names of subcontexts within a naming directory. EachNameComponentrepresents a component of the path to the named object. ANameComponentcontainsidandkindstring fields that serve to label the component in the path. Only theidfield is significant in determining name uniqueness. So aNameComponentwithidset to "student" andkindset to an empty string conflicts with aNameComponentwith anidof "student" andkind"doctoral," if bothNameComponentobjects are relative to the same subcontext. TheNameComponentclass has a constructor that takes theidandkindvalues as arguments. Here's how to create a singleNameComponent: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 ofNameComponentobjects 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 therebind()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 onlyorg.omg.CORBA.Objectreferences can be bound to names within aNamingContext.The following code binds a few of our
ThisOrThatServerobjects 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 aNamingContext. Thebind_new_context()method takes an array ofNameComponentobjects as the relative path of the new context and a reference to theNamingContextobject to bind to that location in the overall directory. A newNamingContextobject can be created from an existing one by calling itsnew_context()method. If a context already exists at the target name, you can use therebind_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.NotFoundexception is thrown.Note that names used in the
bind()orrebind()methods are relative to theNamingContextobject that they're called on. This means we can bind ourThisOrThatServerobject 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
ttDircontext 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.Objectinterface. So, when a client of a remote CORBA object receives a stub for the object, it actually gets anorg.omg.CORBA.Objectthat serves as a proxy for the remote object. Theorg.omg.CORBA.portable.ObjectImplclass provides default implementations for the methods defined onorg.omg.CORBA.Object. Java stubs and implementations for CORBA objects are actually subclassed from theObjectImplclass. Internally,ObjectImpldeals with delegating requests on the object to the proper target object, whether it is a remote object or a local one.ObjectImplimplements theorg.omg.CORBA.Objectinterface and extends thejava.lang.Objectclass, so it truly provides a joining point between the CORBA and Java object environments.A reference to an
org.omg.CORBA.Objectobject 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 theObjectinterface, 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 theObjectreference into a reference of that type, so that you can use the interface to call remote methods directly.Converting an
org.omg.COBRA.Objectto 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 anarrow()method that converts anorg.omg.CORBA.Objectreference to a reference of the given type. If the object reference you pass into thenarrow()method is not the type the helper expects, anorg.omg.CORBA.BAD_PARAMexception is thrown. This is aRuntimeException, 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:
Getting an object reference through a method call on another remote object reference
Using a stringified object reference obtained through a secondary channel and converting it to a live object reference
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
Stringobjects that contains the names of all initial objects in the ORB. These names can then be used to get references to the objects through theresolve_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()andresolve_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
NamingContextreference, you can look up objects within the context by passing names to itsresolve()method. As before, when we were binding objects, a name is represented by an ordered array ofNameComponentobjects. EachNameComponent(both theidfield and thekindfield) 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, anorg.omg.CosNaming.NamingContextPackage.NotFoundexception 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 onThisOrThatServerHelperto "cast" the generic object reference to aThisOrThatServerobject.You can also use the
resolve()method on aNamingContextto get a reference to a subcontext. Just use the path to the context itself andnarrow()it to aNamingContextreference: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
NamingContextobject reference that represents the root of the naming tree for that name directory. Since theNamingContextis itself a CORBA-exported object, one Naming Service can hold a reference to aNamingContextfrom 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
NamingContextobjects 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
ORBinterface,object_to_string()andstring_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
ThisOrThatServerinterface, 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
ServerInitclass contains amain()method that is intended to be run on the server host for our remote object. Themain()method first initializes a connection to the local ORB and then creates an instance of theThisOrThatServerImplclass. This instance serves as the server implementation of our remote object. We create a stringified reference to the object using theobject_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 synchronouswait()on a local object, themain()method goes into a wait state. Thiswait()is necessary to keep the ORB running so that it can respond to client requests. If we let themain()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 itsstring_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 000000Somehow, 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
URLobject. 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 00080000000000000000The 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
Objectfrom a Naming Service, for example, and not know what interface that object implements. I mentioned earlier that you can use anorg.omg.CORBA.Objectreference 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:
RequestA request to invoke a method on a remote object. Created by the client and issued through the ORB to the server object.
NamedValueA named parameter to a method request. Conceptually, this is a name tied to an
Anyvalue. The name of the value must match the name of the parameter as specified in the IDL interface the remote object satisfies.NVListA list of
NamedValueparameters used to represent an argument list passed into a remote method request.AnyA general argument value. An
Anyobject can contain the Java equivalent of any basic IDL type or anObjectthat can be described in IDL.ContextA list of
NamedValueobjects used to specify any details of the client environment that shouldn't be passed as method arguments.Once you get an
org.omg.CORBA.Objectreference 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 aNamedValueobject to hold the result, making aContextobject and putting any useful environment values in it, and then using all of these items to create aRequestobject 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 itsdoThis()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
Objectreference 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
© 2001, O'Reilly & Associates, Inc.