4.3. Putting It in the Public EyeWe 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 ORBSince the ORB is so central to everything in a CORBA environment, the first thing any CORBA process needs to do is get a reference to a local or remote ORB that it can use to find other objects, access CORBA services, and handle remote method calls. A CORBA participant initializes its ORB reference by calling one of the static init() methods on the ORB interface. Each of these methods returns an ORB object that can find CORBA objects and services. The standard init() methods provided on an ORB are as follows (Sun's Java IDL supports all of these standard initialization methods):
There are two standard properties defined for an ORB that can be set in the call to init(), using either the String arguments array or a Properties object. These are the ORBClass and ORBSingletonClass properties, which specify the Java classes to use to create ORBs when an init() method is called. (ORBSingletonClass is a shared ORB instance that is used mainly by generated classes to do things like create TypeCode objects that identify the types of CORBA objects, while ORBClass is a fully functional ORB.) You can use these properties to specify a custom ORB implementation. You may want to override the default ORB implementation (com.sun.CORBA.iiop.ORB in Java IDL) with one of your own that has particular performance characteristics. Or you may be running your CORBA code within an applet and want to ensure that a valid ORB is available no matter what browser version your applet encounters. Sun's Java IDL also adds two nonstandard properties: ORBInitialHost and ORBInitialPort. By default, each ORB.init() method initializes an ORB that looks for its services locally. The current version of the Java IDL API includes a single service, the Naming Service, and the ORB.init() methods assume that the Naming Service is listening to port 900 on the local host. Java IDL adds these two nonstandard properties to allow your local ORB to defer its services (naming, trading, etc.) to a remote ORB running on a given host and listening on a given port. Be careful before you decide to depend on these properties in your application or applet. They are only honored within Sun's Java IDL implementation of the CORBA standard. If you want your CORBA application to be portable to any implementation of the standard IDL-to-Java binding, and you want to use a remote Naming Service, you should stick to using a stringified reference to the remote service, obtained through a secondary communication channel, as we'll discuss shortly. Any of these properties can be specified within a Properties object or as a command-line option to a Java application. As an example, if you want to specify a different host to use for finding services like the Naming Service, one way to do this is to specify the host explicitly in the code that initializes the ORB, using a Properties object: Properties orbProps = new Properties(); orbProps.put("org.omg.CORBA.ORBInitialHost", "remote.orb.com"); ORB myOrb = ORB.init((String[])null, orbProps); Alternately, you can take command-line arguments passed into your Java code and pass them to the ORB.init() method to be parsed. Say we have a class named InitRemote with a main method implemented as follows: public class InitRemote { public static void main(String[] argv) { try { ORB myOrb = ORB.init(argv, null); ... } } } In this case, we can specify any ORB properties on the command line using specific argument names: orbhost% java InitRemote -ORBInitialHost remote.orb.com Note that you can use the second ORB.init() method with both a String arguments array and a Properties list specified, even though the examples here haven't shown that. 4.3.2. Registering with a Naming ServiceOne way to make a server object available to remote clients is to register it with the local CORBA Naming Service under a specific name. A remote client can then get a reference to the root NamingContext for the Naming Service and ask for the server object by name. Example 4-8 shows a class whose main() method creates an instance of our ThisOrThatServer implementation and then registers the object with the Naming Service. The program starts by getting a reference to the local ORB. Then it asks the ORB for a reference to the Naming Service (using the standard name "NameService") with the resolve_initial_references() method. This reference is actually the root NamingContext, so we narrow the object reference using NamingContextHelper. We register the ThisOrThatServer with the Naming Service by building an array of NameComponent objects and then calling the rebind() method on the NamingContext. With the object registered, we go into a wait state, waiting for client requests. Example 4-8. Registering an Object with the Naming Serviceimport org.omg.CORBA.*; import org.omg.CosNaming.*; public class ServerNamingInit { public static void main(String[] argv) { try { // Obtain ORB reference ORB myORB = ORB.init(argv, null); // Make a ThisOrThatServer object to register with the ORB ThisOrThatServer impl = new ThisOrThatServerImpl(); // Get the root name context org.omg.CORBA.Object objRef = myORB.resolve_initial_references("NameService"); NamingContext nc = NamingContextHelper.narrow(objRef); // Register the local object with the Name Service NameComponent ncomp = new NameComponent("ThisOrThatServer", ""); NameComponent[] name = {ncomp}; nc.rebind(name, impl); // Go into a wait state, waiting for clients to connect System.out.println("Waiting for clients..."); java.lang.Object dummy = new String("I wait..."); synchronized (dummy) { dummy.wait(); } } catch (Exception e) { System.out.println("Error occurred while initializing server object:"); e.printStackTrace(); } } } Note that Example 4-8 imports the org.omg.CORBA and org.omg.CosNaming packages, which are the two main packages in Java IDL. Because of the naming collision between java.lang.Object and org.omg.CORBA.Object, we have to use the fully qualified names of these two classes when we use them in CORBA applications that import org.orm.CORBA. Finally, note that this example binds the ThisOrThatServer object within the root NamingContext using the name "ThisOrThatServer". We'll see shortly how to create subcontexts and bind objects within them. Before running this initialization of our CORBA object, we need to start a Naming Service on the host for the object. A Naming Service daemon listens for Naming Service requests on a specific port and provides access to the named object directory it manages. In Java IDL, the Naming Service is started using the tnameserv command: objhost% tnameserv & With that done, we can run our initialization method to register our server object with the ORB: objhost% java ServerNamingInit 4.3.3. Adding Objects to a Naming ContextInitially, a CORBA naming directory is empty, with only its root NamingContext and no objects. The bind() method on a NamingContext object binds a server object to a name within the context. The bind_new_context() method creates new subcontexts within a given NamingContext. Using a file directory analogy, calling bind_new_context() on a NamingContext object is like making a new subdirectory, while calling bind() puts a new file into a directory. The Java IDL mapping uses arrays of NameComponent objects to represent the names of subcontexts within a naming directory. Each NameComponent represents a component of the path to the named object. A NameComponent contains id and kind string fields that serve to label the component in the path. Only the id field is significant in determining name uniqueness. So a NameComponent with id set to "student" and kind set to an empty string conflicts with a NameComponent with an id of "student" and kind "doctoral," if both NameComponent objects are relative to the same subcontext. The NameComponent class has a constructor that takes the id and kind values as arguments. Here's how to create a single NameComponent: NameComponent comp1 = new NameComponent("student", "doctoral"); A complete name path can be composed as an array of these objects: NameComponent path[] = { comp1, comp2, ... }; The bind() method takes two arguments: an array of NameComponent objects as the relative name for the object you're putting into the Naming Service and the server object itself. If you're binding a server object using the root context of the Naming Service, the name is also the absolute name of the object in the overall naming directory. If an object is already bound to the name, you can use the rebind() method with the same arguments, causing the existing object bound to that name to be replaced by the new object. Note that since the Naming Service is a CORBA service that can be accessed remotely by other CORBA clients, the objects it contains need to be exportable to these remote clients. This means that only org.omg.CORBA.Object references can be bound to names within a NamingContext. The following code binds a few of our ThisOrThatServer objects to names within the root context of a Naming Service: // Get the root naming context ORB myORB = ORB.init(...); org.omg.CORBA.Object ref = myORB.resolve_initial_references("NameService"); NamingContext rootNC = NamingContextHelper.narrow(ref); // Create a few servers org.omg.CORBA.Object ref1 = new ThisOrThatServerImpl(); org.omg.CORBA.Object ref2 = new ThisOrThatServerImpl(); // Bind them to names in the Naming Service NameComponent name1 = new NameComponent("server1", ""); NameComponent path1[] = { name1 }; NameComponent name2 = new NameComponent("server2", ""); NameComponent path2[] = { name2 }; rootNC.bind(path1, ref1); rootNC.bind(path2, ref2); Before you can bind an object to a name with multiple components, all the subcontexts (subdirectories) have to be created using the bind_new_context() method on a NamingContext. The bind_new_context() method takes an array of NameComponent objects as the relative path of the new context and a reference to the NamingContext object to bind to that location in the overall directory. A new NamingContext object can be created from an existing one by calling its new_context() method. If a context already exists at the target name, you can use the rebind_context() method to replace the existing context with a new one. This is useful for emptying out an entire subcontext without removing each object individually. Here is an example that binds some objects within various subcontexts: // Get the root context, as before NamingContext rootNC = ...; // Create the components to the subcontext name NameComponent comp1 = new NameComponent("servers", ""); NameComponent ttComp = new NameComponent("ThisOrThat", ""); NameComponent otherComp= new NameComponent("misc", ""); // Create each subcontext within the root context and bind them // to their appropriate names // Create a new context, bind it to the name "servers" // off the root NamingContext NamingContext context1 = rootNC.new_context(); NameComponent path1[] = { comp1 }; rootNC.bind_context(path1, context1); // Create another context, bind it to the name "servers, ThisOrThat" NamingContext ttDir = rootNC.new_context(); NameComponent path2_1[] = { comp1, ttComp }; rootNC.bind_context(path2_1, ttDir); // Create another context, bind it to the name "servers, misc" NamingContext otherDir = rootNC.new_context(); NameComponent path2_2[] = { comp1, otherComp }; rootNC.bind_context(path2_2, otherDir); // Now we can bind servers to a name within any of the new subcontexts org.omg.CORBA.Object ttRef = new ThisOrThatServerImpl(); org.omg.CORBA.Object otherRef = new SomeOtherServerImpl(); // Bind the other server to the "misc" branch of the "servers" dir. NameComponent yetAnotherComp = new NameComponent("SomeOtherServer", ""); NameComponent otherPath[] = { comp1, otherComp, yetAnotherComp }; rootNC.bind(otherPath, otherRef); // Bind the ThisOrThatServer to the appropriate branch under "servers" NameComponent tt1Comp = new NameComponent("server1", ""); NameComponent ttPath[] = { comp1, ttComp, tt1Comp }; rootNC.bind(ttPath, ttRef); If you try to bind an object or a subcontext to a name within a context that hasn't been created yet, a org.omg.CosNaming.NamingContextPackage.NotFound exception is thrown. Note that names used in the bind() or rebind() methods are relative to the NamingContext object that they're called on. This means we can bind our ThisOrThatServer object in the previous example to the same absolute name within the directory by replacing the last two lines of the example with the following: NameComponent relObjPath[] = { tt1Comp }; ttDir.bind(relObjPath, ttRef); The ttDir context is bound to the {"servers", "ThisOrThat"} subdirectory, so binding an object to the name {"server1"} within this context is equivalent to binding it to the full path {"servers", "ThisOrThat", "server1"} from the root context. You can use similar shorthand when binding new contexts within a directory. In other words, you can bind a context to a relative name within a subcontext, instead of an absolute name within the root context. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|