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. We must instantiate one or more instances of our CORBA server implementation and connect them to an ORB on the server so that they can receive remote method requests. Then clients need to somehow obtain remote references to our server objects. There are two fundamental ways that a client obtains a remote reference: it can get an initial object reference (usually to a CORBA service of some kind, like a Naming Service) using the ORB.resolve_initial_references( ) method and somehow find a reference to the remote object through method calls on the initial object, or it can get a “stringified” reference to the remote object (either an Interoperable Object Reference or an Interoperable Naming Service URL) and use the local ORB to convert it to a live object reference.

For the first case, a remote object needs to be registered in some way with a server-side ORB, and probably with a CORBA service of some kind. Connecting your CORBA object to an ORB enables it to accept remote method requests from clients. Registering your object with a CORBA service makes it easier for remote clients to find your object in the first place.

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 this, then look at registering the remote object with a Naming Service (the most common way to “publish” remote objects in a CORBA context). Finally, we’ll look at two ways that clients can use stringified references to access your CORBA object.

Initializing the ORB

Since the ORB is so central to everything in a CORBA environment, the first thing any CORBA process needs to do (whether it’s a CORBA server or client process) is get a reference to an 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, among other things. 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, called the “singleton ORB.” Each call within the same runtime environment of this version of init( ) returns the same ORB reference. If used within an applet context, the ORB has limited abilities. The singleton ORB is used mainly by IDL-generated classes to perform actions like create TypeCode objects that identify the types of CORBA objects.

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:

org.omg.CORBA.ORBSingletonClass

Specifies the class to use for the singleton ORB instance returned by the no-argument version of init( ).

org.omg.CORBA.ORBClass

Creates ORB instances when the other versions of init( ) are called.

In both cases, the class name you provide has to implement the org.omg.CORBA.ORB interface. You can use these two properties to specify a custom ORB implementation, if needed. 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, for example. 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 (naming service, for example) locally. 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 object, obtained through a secondary communication channel (as we’ll discuss shortly).

Any of these properties can be specified within a Properties object, as a command-line option to a Java application, or in a system properties file. 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. Notice that when specifying these, you should omit the org.omg.CORBA package prefix on the property 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.

Registering with a Naming Service

One way to make a server object available to remote clients is to register it with a 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 look up the server object by name.

Regardless of whether you’re using a POA-compliant CORBA environment, here are the basic steps that create and register a CORBA object with the ORB:

  • Initialize an ORB reference.

  • Create an instance of your CORBA server implementation, and get a reference from it that can be registered with the naming service.

  • Get a reference to the root NamingContext for the Naming Service from the ORB.

  • Construct a name for the object that reflects where you want it to sit in the Naming Service hierarchy.

  • Bind your object to that name in the Naming Service, using the root NamingContext.

The mechanics of carrying out each step are slightly different in the POA-compliant and pre-POA versions of Java IDL. We’ll start by looking at the pre-POA version, shown in Example 4-5. This code listing shows a utility class named AccountInitPrePOA, whose main( ) method creates an instance of our AccountImplPrePOA implementation and then registers the object with the Naming Service. The program starts by getting a reference to the local ORB, as discussed in the previous section. Then it asks the ORB for a reference to its registered Naming Service by calling the resolve_initial_references( ) method on the ORB, using the standard name “NameService.” This object reference is the root NamingContext of the Naming Service, so we narrow the object reference using NamingContextHelper. Next, we build the name to use to register the object with the Naming Service. The name of an object in the basic CORBA Naming Service is represented by an array of NameComponent objects, one for each branch in the hierarchy. Here, we’re assuming that the user has passed in a simple string as a command-line argument, and we use it to construct a NameComponent array with a single element (representing a simple one-level path for the object in the naming hierarchy). If we wanted to register our object using a multilevel path, we first must create the subcontexts, then register the object (we’ll see how that works in a later section). Once the NameComponent array is constructed, we call the rebind( ) method on the NamingContext to register our Account object with that name. With the object registered, we send this thread into a perpetual wait state (by creating a dummy String variable and doing a synchronized wait( ) on it). This keeps the thread alive so the Account object can service client requests.

Example 4-5. Creating/Registering an Account Server Object (pre-POA)

//
// Initialize a remote Account object and register it with the Naming Service
//

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

public class AccountInitPrePOA {
  public static void main(String[] args) {
    try {
      // Initialize an ORB reference
      ORB myORB = ORB.init(args, null); 

      // Create an instance of an Account server implementation
      Account impl = new AccountImplPrePOA(args[0]);

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

      // 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(args[0], "");
      NameComponent[] name = {ncomp};
      nc.rebind(name, impl);

      System.out.println("Registered account under name " + args[0]);

      // 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(
             "An error occurred while initializing server object:");
      e.printStackTrace(  );
    }
  }
}

Before running this utility object to create and register our CORBA object, we need to start a Naming Service someplace. 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 oreilly.jent.corba.AccountInitPrePOA JimF
Registered account under name JimF

Performing these same tasks under the POA-compliant version of Java IDL (JDK 1.4 and later) is fairly similar, except that using the Portable Object Adaptor adds some new twists in the creation of the server object and its reference. Example 4-6 shows the code listing for AccountInitPOA, which performs the same task as AccountInitPrePOA, but within the POA-compliant version of Java IDL. One key difference in this version is the additional POA operations immediately following the creation of the Account server object. Since we’re dealing with an ORB using the POA, and the POA is acting as the mediator between our server object and the ORB, we need to use the POA to obtain a reference to our server object that can be used for registration with the Naming Service (among other things). So we get a reference to the POA from the ORB using the resolve_initial_references( ) method (this time using the standard name for the POA, “RootPOA”). We then make sure that the POA is active by getting its POA manager and calling its activate( ) method -- every POA has a POAManager that maintains the runtime state of the POA. Finally, we ask the POA for a usable reference to our server object by calling its servant_to_reference( ) method and converting the resulting Object to an Account reference. For more details on the various POA interfaces and their methods, refer to Chapter 14.

Example 4-6. AccountInitPOA.java

//
// CORBA example 9: Initialize a remote object, register it with a naming
// service, and generate a stringified IOR.
//

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

public class AccountInitPOA {
  public static void main(String[] args) {
    try {
      // Initialize an ORB reference
      ORB myORB = ORB.init(args, null); 

      // Create an instance of an Account server implementation
      AccountImplPOA acct = new AccountImplPOA(args[0]);

      // Get the root Portable Object Adapter (POA)
      POA rootPOA = (POA)myORB.resolve_initial_references("RootPOA");

      // Activate the POA manager
      rootPOA.the_POAManager().activate(  );
      
      // Get an object reference from the implementation
      org.omg.CORBA.Object obj = rootPOA.servant_to_reference(acct);
      Account acctRef = (Account)AccountHelper.narrow(obj);
      
      // Get the root name context (use the INS interface, so that we can use
      // the simpler name construction process)
      org.omg.CORBA.Object objRef = 
        myORB.resolve_initial_references("NameService");
      NamingContextExt nc = NamingContextExtHelper.narrow(objRef);

      // Register the local object with the Name Service
      // Use the Interoperable Naming Service interface to simplify matters,
      // and to support URL-formatted names (e.g. "JohnS",
      // "corbaname://orbhost.com#/JohnS", etc.)
      NameComponent[] name = nc.to_name(args[0]);
      nc.rebind(name, acctRef);

      System.out.println("Registered account under name " + args[0]);
      
      // Go into a wait state, waiting for clients to connect
      myORB.run(  );
    }
    catch (Exception e) {
      System.out.println(
                      "An error occurred while initializing server object:");
      e.printStackTrace(  );
    }
  }
}

Another difference in this version of our object initialization utility is the actual registration of the server object with the Naming Service. Here, since JDK 1.4 also introduced Java support for the Interoperable Naming Service (INS) interface, we can use the NamingContextExt interface to make binding objects a bit easier. We’ll get into the details of the different Naming Service interfaces in the next section.

The last difference in this version of our initialization utility is in how we cause the thread to wait for client requests. The CORBA 2.3 (and higher) versions of the ORB interface include a run( ) method. Calling this method causes the current thread to wait until the ORB shuts down. This is a clean way to accomplish the same task as our dummy.wait( ) approach in the previous version of this utility class.

Now that we’ve seen the basic steps involved in creating and registering an object with a CORBA Naming Service, let’s look in more detail at binding objects and new contexts within the Naming Service.

Adding Objects to a Naming Context

As was discussed earlier, a CORBA naming service maintains a hierarchy of named objects that can be looked up by remote clients. 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 from the current directory, while calling bind( ) puts a new file into the current 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 an id set to “student” and kind set to an empty string conflicts with a NameComponent with an id of “student” and a kind of “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 snippet binds a few of our Account 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 Accounts
Account acct1 = new AccountImplPOA("JohnSmith");
Account acct2 = new AccountImplPOA("MaryJones");

// Bind them to names in the Naming Service
NameComponent name1 = new NameComponent("Smith,J", "");
NameComponent path1[] = { name1 };
NameComponent name2 = new NameComponent("Jones,M", "");
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 Account objects within various subcontexts representing different branches of a bank, using the standard Naming Service interfaces; NameComponent and NamingContext (both from the org.omg.CosNaming package). This example shows how to do this with a POA-compliant ORB -- the code is very similar for the pre-POA versions of Java IDL:

// Initialize the ORB and get the POA and root naming context, as before
ORB myORB = ORB.init(...);
POA rootPOA = ...;
NamingContext rootNC = ...;

// Create the components to the sub-context paths
NameComponent branchComp = new NameComponent("bankBranches", "");
NameComponent cambridgeComp = new NameComponent("Cambridge", "");
NameComponent bostonComp= new NameComponent("Boston", "");

// Create a new context, bind it to the path "bankBranches"
NamingContext ctx = rootNC.new_context(  );
NameComponent path[] = { branchComp };
rootNC.bind_context(path, ctx)

// Create another context, bind it to the path "bankBranches, Cambridge" 
NamingContext cambridgeCtx = rootNC.new_context(  );
path = { branchComp, cambridgeComp };
rootNC.bind_context(path, cambridgeCtx)

// Create another context, bind it to the path "bankBranches, Boston" 
NamingContext bostonCtx = rootNC.new_context(  );
path = { branchComp, bostonComp };
rootNC.bind_context(path, bostonCtx);

// Now we can bind Accounts to a name within any of the new sub-contexts
// Create a few Account server objects, and get usable client references
// from the POA
Account johnAcct = new AccountImplPOA("JohnSmith");
Account johnRef =
      AccountHelper.narrow(rootPOA.servant_to_reference(johnAcct));
Account maryAcct = new AccountImplPOA("MarkJones");
Account maryRef =
      AccountHelper.narrow(rootPOA.servant_to_reference(maryAcct));

// Bind each Account to a name in the appropriate branch path. Assume
// that John has his account out of the Cambridge branch, Mary has hers
// out of the Boston branch.
NameComponent johnComp = new NameComponent("Smith,J", "");
NameComponent johnPath[] = { branchComp, cambridgeComp, johnComp };
rootNC.bind(johnPath, johnRef);
NameComponent maryComp = new NameComponent("Jones,M", "");
NameComponent maryPath[] = { branchComp, bostonComp, maryComp };
rootNC.bind(maryPath, maryRef);

If you try to bind an object or a subcontext to a name within a context that hasn’t been created yet, an 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 Mary’s Account 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 maryRelativePath[] = { maryComp };
bostonCtx.bind(maryRelativePath, maryRef);

The bostonCtx context is bound to the { "bankBranches" , "Boston" } subdirectory, so binding an object to the name { "Jones,M" } within this context is equivalent to binding it to the full path { "bankBranches" , "Boston" , "Jones,M" } 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.

A slightly simpler approach: The Interoperable Naming Service

The Interoperable Naming Service (INS) interfaces make both the binding and lookup of objects in a Naming Service a bit easier. In our previous examples, we bound objects to specific locations in the naming hierarchy by first creating the subcontexts (subdirectories) we needed, then binding the remote objects to names within those contexts. In each step, we needed to construct fully qualified names for both the contexts and the objects we were binding. Prior to the introduction of the INS, the only way to do this is the way shown earlier examples: manually construct an array of NameComponents representing the intended path to the object. Using the INS interfaces, and their support for new types of stringified object references and URLs, it’s possible to create new contexts and bind objects in a slightly simpler way. We can convert stringified object references directly to NameComponent arrays using the to_name( ) method on the org.omg.CosNaming.NamingContextExt interface. For example, assuming that we’ve created the necessary subcontexts already, we could bind John Smith’s account to the same path as before by specifying the full path as a string and converting it using to_name( ):

NamingContextExt rootNC = ...;
String johnURL = "bankBranches/Cambridge/Smith,J";
NameComponent johnPath[] = rootNC.to_name(johnURL);
rootNC.rebind(johnPath, johnRef);

Get Java Enterprise in a Nutshell, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.