Book Home Java Enterprise in a Nutshell Search this book

4.4. Finding Remote Objects

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

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

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

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

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

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

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

4.4.1. Initial ORB References

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

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

String names[] = myORB.list_initial_references();

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

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

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

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

4.4.2. Getting Objects from Other Remote Objects

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

4.4.2.1. Using a naming context

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

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

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

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

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

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

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

4.4.2.2. Using multiple naming services

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.4.3. Stringified Object References

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

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

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

Example 4-9. Registering an Object/Getting Its Stringified Object Reference

import org.omg.CORBA.*;

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

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

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

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

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

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

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

Example 4-10. A Client Utilizing a Stringified Object Reference

import org.omg.CORBA.*;

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

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

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

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

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

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

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

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

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

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



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.