Finding and Using Remote Objects

Now that we’ve seen how to implement a CORBA remote object and register it with an ORB, we can turn our attention to the heart of the matter: client applications that want to use the object. As we said earlier, every CORBA process needs a reference to an ORB. Once a client application has access to an ORB, next it must 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 in the CORBA environment.

The whole point of CORBA is to be able to distribute objects across the network and use them from any point. 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.

Remote Object References and Narrowing

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 within 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, because some object-programming languages may have different inheritance structures. In the Java binding for CORBA, all CORBA object references (local or remote) implement the org.omg.CORBA.Object interface. So, when a client of a remote CORBA object receives a stub for the object, it actually gets an org.omg.CORBA.Object that serves as a proxy for the remote object. The org.omg.CORBA.portable.ObjectImpl class provides default client-side implementations for the methods defined on org.omg.CORBA.Object. Java stubs (generated from the IDL-to-Java compiler, as discussed earlier) for CORBA objects are actually subclassed from the ObjectImpl class. Internally, ObjectImpl deals with delegating client requests on the object stub to the proper target object, whether it’s a local or remote object. ObjectImpl implements the org.omg.CORBA.Object interface and inherently extends the java.lang.Object class, so it provides a joining point between the CORBA and Java object environments.[14]

A reference to an org.omg.CORBA.Object instance that is connected to a remote object is actually 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 directly through the generic Object interface, as we’ll discuss later in this chapter. If your client has the actual Java interface for the remote object available at compile time, however, you probably want to convert the Object reference into a reference of that type, so you can use the interface to call remote methods directly.

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

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 can’t be found locally (see Chapter 3 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 native type when a client obtains a stub reference. In Java you could do this using the same remote classloading scheme as RMI, but there isn’t a similar scheme available in C or C++, for example. As a result, the stub reference is initially represented by a basic object type that knows how to forward method requests to its server object, but doesn’t necessarily satisfy the mapped interface for the remote object type the client is expecting. The client application is forced to “cast” this stub to the correct local type, using the appropriate narrow( ) method. In the Java mapping of IDL, this means calling the narrow( ) method on the corresponding helper class. The narrow( ) method converts the reference, making a type-specific stub interface that also includes the remote object reference information needed to forward method requests to the actual object implementation.

Accessing Remote Objects

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 initial reference directly from the ORB

  • 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

ORB Initial Object 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 typically available through the ORB directly, based on how it is configured. The ORB interface provides the resolve_initial_references( ) method for obtaining references to the objects that represent these services. Each CORBA service 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 CORBA objects, the standard name for the Naming Service is “NameService,” and the object representing access to the Naming Service is a NamingContext or NamingContextExt object. These represent the root of the naming hierarchy.

Once you’ve initialized your ORB reference (as described earlier in Initializing the ORB), you can ask the ORB for a list of the names of the initial objects it has available, using the list_initial_references( ) method:

String names[] = myORB.list_initial_references(  );

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

Here’s how we used resolve_initial_references( ) to obtain a reference to the Naming Service in our earlier examples:

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

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

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. The most 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.

Naming service lookups

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

So, if a client wants to find the account for Mary Jones, we stored in the last binding example, which needs to do the following:

// Set up the full path to the account
NameComponent comp1 = new NameComponent("bankBranches", "");
NameComponent comp2 = new NameComponent("Cambridge", "");
NameComponent acctName = new NameComponent("Jones,M", "");
NameComponent acctPath[] = { comp1, comp2, acctName };
// Find the object in the directory
org.omg.CORBA.Object acctRef = rootNC.resolve(acctPath);
Account acct = AccountHelper.narrow(acctRef);

We’re assuming that rootNC is a reference to the root context of the Naming Service holding the Account object references. Note the use of the narrow( ) method on AccountHelper to “cast” the generic object reference to an Account object.

In addition to finding references to objects (leaf nodes) in the Naming Service, you can also use the resolve( ) method on a NamingContext to get a reference to a subcontext (a subdirectory). Just use the path to the context itself and narrow( ) it to a NamingContext reference:

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

Object URLs

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. CORBA defines several ways to represent stringified object references, or remote object URLs. In other words, you can fully specify a specific CORBA server object using a URL syntax, which is very similar to the URL syntax used to specify Java RMI objects in an RMI registry (see Chapter 3 for more details on RMI registry URLs). These object URLs are called “stringified object references” in the CORBA specifications, because that’s exactly what they are: strings that can be sent around the network (in email, in configuration files, etc.), and used by clients to obtain a living reference to the object that the URL specifies. So these object URLs are called “stringified object references” in the same way that you could call HTML document URLs “stringified document references.”

In these URL schemes, the objects being referenced need to be hosted behind an ORB someplace on the network, and they must have a server process running that is listening for remote method requests for that object. In some cases; the objects and their request servers can just be connected directly to the ORB, in other cases they have to be bound to a name in a Naming Service, with the Naming Service acting as the method request mediator.

There are three basic forms of object URLs defined in the CORBA standards:

Interoperable Object References (IORs)

These URLs are based on a syntax for representing a remote object reference in the form of a printable, but not human-readable, string of characters. This stringified object reference includes enough information for a remote CORBA client to locate the object’s home ORB and convert the string to a runtime stub reference to the object. Two methods on the ORB interface, object_to_string( ) and string_to_object( ), let you convert a CORBA object reference to an IOR and back into an object reference. An object doesn’t need to be bound to a name in a Naming Service in order to have an IOR; it simply needs to be exported through an ORB using an object adaptor, like the POA.

corbaloc URLs

These URLs are a human-readable URL format for object references. They are similar in purpose to IORs: an object needs to be exported through an ORB for remote access in order to have a corbaloc URL associated with it. So corbaloc URLs point to a single object accessible directly through an ORB, without the help of a service of some kind. The same restrictions apply for making servants accessible through corbaloc URLs as for IORs -- the servant object needs to be connected to an ORB through some kind of object adaptor, like the POA.

corbaname URLs

These URLs are extensions to the corbaloc syntax to allow for references to objects that are registered with a CORBA Naming Service. The URL format includes additional information that specifies the object’s location (name) in the Naming Service.

The full syntax for all three of these CORBA object URL schemes is spelled out fully in Chapter 15. But the following examples should demonstrate their basic use.

Example 4-7 shows AccountInit, a server that creates an instance of our AccountImplPOA and makes it available for clients to access. In its main( ) method, it initializes the ORB, then creates an instance of our Account servant. We’re assuming that a POA-compliant version of Java IDL is being used, so the next thing to do is get a reference to the root POA (using resolve_initial_references( )) and activate the POA manager, then ask the POA to activate our servant object. By activating our servant object this way, we’re guaranteed that any “direct” IIOP requests (through either an IOR or a corbaloc URL) will be accepted immediately. Note that in order for the generated IOR 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, we’ll assume that our client and object server are both running IIOP.

Example 4-7.  Initializing an Account/Getting Stringified References

//
// AccountInit: Initialize an Account object, register it with a naming
// service, and generate URLs for the object.
//

import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.PortableServer.POA;
import java.net.InetAddress;
public class AccountInit {

  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(  );
      // Activate our servant, so that corbaloc and IOR requests will work
      // immediately
      rootPOA.activate_object(acct);

      // 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]);
      System.out.println("New account created and registered.  URLs are: ");
      System.out.println("\nIOR");
      System.out.println("\t" + myORB.object_to_string(acctRef));
      System.out.println("\ncorbaname");
      System.out.println("\tcorbaname:iiop:" +
                         InetAddress.getLocalHost().getHostName(  ) +
                         "#" + 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(  );
    }
  }
}

Next, we ask the POA to generate an Account reference for us from the servant object; we use this reference to register the Account with the Naming Service. The request to convert the reference to an Account reference is necessary in POA-compliant environments, because the object implementation class doesn’t directly extend the mapped object interface (in POA server skeletons we saw how the generated skeleton extends the AccountOperations interface, not the Account interface).

Now that the server object is both connected to the ORB through the POA and registered with a Naming Service, we can generate various URLs for this object that clients can use to get direct references to the object. First we get the IOR of the object, using the object_to_string( ) method on the ORB. Then we create the corbaname URL for the object, using the local hostname, the port number of the ORB, and the name the user passed in on the command line and used to register the object with the Naming Service. Each of these is printed to the console, and we can distribute these URLs to the clients that need them. Note that we aren’t creating a corbaloc URL for our object. This is because the server-side creation of corbaloc URLs is not standardized; CORBA vendors currently provide nonstandard ways to generate these in their implementations.

Notice we’re only showing how to initialize the server object in a POA-compliant environment. In the pre-POA versions of Java IDL, instead of activating the object using the POA interface, you would simply “connect” the object to the local ORB:

Account acctRef = new AccountImplPrePOA(...);
myORB.connect(acctRef);

You also wouldn’t need to perform the conversion into an Account reference, since in the ImplBase inheritance model, the server skeleton _AccountImplBase directly extends the generated Account interface, and can be used to register the object with the Naming Service.

To demonstrate using these URLs, Example 4-8 shows a simple client for our Account server. This client uses a command-line interface and allows the user to perform transactions on a given Account, by specifying the Account to use (using its object URL), a transaction type (“deposit”, “withdrawal” or “balance”), and an optional amount (for deposits and withdrawals). After doing the standard ORB initialization, the client first checks the name of the Account object (the first command-line argument). If the name of the Account is an object URL (IOR, corbalo, or corbaname), then the client simply uses the string_to_object( ) method on the ORB to get a reference to the specified Account object, and narrows the returned Object to an Account reference using the AccountHelper. If some other string is passed in, the client assumes the string is a name to be used to do a lookup in the Naming Service accessible from the ORB, and performs the lookup using the standard Naming Service operations (described earlier). Once the client has the reference to the remote Account object, the rest is easy -- just parse the remaining command-line options given by the user, and perform the requested transaction on the account.

Example 4-8. A Client for the Account Servant

import org.omg.CORBA.*;
import org.omg.CORBA.ORBPackage.*;
import org.omg.CosNaming.*;
import java.util.*;

/**
 * AccountClient: A client that looks up the account "named" on the
 *    command-line, then performs the requested transaction on the account.
 */

public class AccountClient {
  public static void main(String args[]) {
    // Initialize the ORB
    ORB orb = ORB.init(args, null);
    org.omg.CORBA.Object ref = null;
    // The object name passed in on the command line
    String name = args[0];
    Account acct = null;
    // See if a stringified object reference/URL was provided
    if (name.startsWith("corbaname") || name.startsWith("corbaloc") ||
        name.startsWith("IOR")) {
      System.out.println("Attempting to lookup " + args[0]);
      ref = orb.string_to_object(args[0]);
      acct = AccountHelper.narrow(ref);
    }
    // Otherwise, do a traditional Naming Service lookup using the
    // services being referenced by our local ORB
    else {
      try {
        ref = orb.resolve_initial_references("NameService");
      }
      catch (InvalidName invN) {
        System.out.println("Couldn't locate a Naming Service");
        System.exit(1);
      }
      NamingContext nameContext = NamingContextHelper.narrow(ref);
      NameComponent comp = new NameComponent(args[0], "");
      NameComponent path[] = {comp};
      try {
        ref = nameContext.resolve(path);
        System.out.println("ref = " + ref);
        acct = AccountHelper.narrow(ref);
      }
      catch (Exception e) {
        System.out.println("Error resolving name against Naming Service");
        e.printStackTrace(  );
      }
    }

    if (acct != null) {
      // We managed to get a reference to the named account, now check the
      // requested transaction from the command-line
      String action = args[1];
      float amt = 0.0f;
      if (action.equals("deposit") || action.equals("withdrawal")) {
        amt = Float.parseFloat(args[2]);
      }
      System.out.println("Got account, performing transaction...");
      try {
        // Did user ask to do a deposit?
        if (action.equals("deposit")) {
          acct.deposit(amt);
          System.out.println("Deposited " + amt + " to account.");
          System.out.println("New balance = " + acct.getBalance(  ));
        }
        // Did user ask to do a withdrawal?
        else if (action.equals("withdrawal")) {
          acct.withdraw(amt);
          System.out.println("Withdrew " + amt + " from account.");
          System.out.println("New balance = " + acct.getBalance(  ));
        }
        // Assume a balance inquiry if no deposit or withdrawal was given
        else {
          System.out.println(
              "Current account balance = " + acct.getBalance(  ));
        }
      }
      catch (InsufficientFundsException ife) {
        System.out.println("Insufficient funds for transaction.");
        System.out.println("Current account balance = " + acct.getBalance(  ));
      }
      catch (Exception e) {
        System.out.println("Error occurred while performing transaction:");
        e.printStackTrace(  );
      }
    }
    else {
      System.out.println("Null account returned.");
      System.exit(1);
    }
  }
}

To run this example, we first have to have a Naming Service running somewhere, then initialize an Account object for the client to use:

orbhost% tnameserv &
Initial Naming Context:
IOR:000000000000002b49444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696
e67436f6e746578744578743a312e30000000000001000000000000007c00010200000000
0a3132372e302e302e3100041a00000035afabcb0000000020363e1ad7000000010000000
0000000010000000d544e616d655365727669636500000000000000040000000003000000
0000000100000001000000200000000000010001000000020501000100010020000101090
000000100010100
TransientNameServer: setting port for initial object references to: 900
Ready.
orbhost% java oreilly.jent.corba.AccountInit JimF
Registered account under name JimF
New account created and registered. URLs are:

IOR           
IOR:000000000000002349444c3a6f7265696c6c792f6a656e742f636f7262612f4163636
f756e743a312e300000000000010000000000000068000102000000000a3132372e302e30
2e3100046600000021afabcb0000000020363f810b0000000100000000000000000000000
4000000000300000000000001000000010000002000000000000100010000000205010001
0001002000101090000000100010100

corbaname
        corbaname:iiop:orbhost#JimF

To perform transactions on this newly created and registered Account object, we run the AccountClient, passing in one of these object URLs as the “name” of the Account. Here we’ll use the corbaname URL.

myclient% java oreilly.jent.corba.AccountClientcorbaname:iiop:orbhost#JimF
Attempting to lookup corbaname:iiop:orbhost#JimF
Got account, performing transaction...
Deposited 1000.0 to account.
New balance = 1000.0


[14] In pre-POA versions of Java IDL (JDK 1.2 and 1.3), the server skeletons generated for CORBA interfaces were also subclassed from ObjectImpl. In POA-compliant versions, the skeletons are subclassed from org.omg.PortableServer.Servant, which provides an alternative implementation of the org.omg.CORBA.Object interface, as well as a number of methods specific to servant objects.

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.