Introduction to Orbix C++ Edition


Chapter 5

The Naming Service

The standard CORBA Naming Service is introduced and programming examples are given. A non-standard wrapper class, with convenient features for accessing the naming service, is also presented.

Many large scale applications use a CORBA-compliant naming service such as OrbixNames as a centralized object repository. OrbixNames is IONA Technologies' implementation of the CORBA Naming Service specification. The role of OrbixNames is to allow a name to be associated with a CORBA object, and to allow that object to be found subsequently by resolving that name. Instead of requiring that a client bind to a specific server on some host, the server binds a name-to-object mapping in the names repository. The client resolves that object reference by looking up the name. The name still needs to be available to the client, but it need not be concerned with the object's server location.

OrbixNames Concepts

OrbixNames maintains a database of bindings between names and object references. A binding is an association between a name and an object. The naming service provides operations to resolve a name, to create new bindings, to delete existing bindings, and to list the bound names.

A name is always resolved within a given naming context. The naming context objects in the system are organized into a naming graph, which may form a naming hierarchy, much like that of a filing system.

Figure 5.1 illustrates the components of the application:

Figure 5.1: Using OrbixNames in Orbix
  1. A StockWatch server publishes StockWatch object references in OrbixNames. OrbixNames maps names to these StockWatch object references, thereby maintaining a repository of name associations in the form of a database.
  2. Clients resolve names in OrbixNames.
  3. Clients remotely invoke operations on object references in the StockWatch server.

Naming Service IDL

Figure 5.2 shows a tree-like hierarchy in a naming service. The black discs represent naming contexts and the open discs represent object bindings. At the root of the hierarchy is the root naming context. This is generally the point of entry to the naming service: all other naming contexts and name bindings are accessible from the root. In Figure 5.2 are shown two other naming contexts example1 and example2. The naming context example1 shows two bindings oracle and sqlsrv.

Figure 5.2: StockWatch Naming Context Hierarchy

A convenient string format is used to refer to entries in the naming service--a format used by the OrbixNames command-line utilities. Using this format1, the bindings under the naming context example1 can be denoted as "example1.oracle" and "example1.sqlsrv". The '.' (dot) character is used to separate the components of a name.

Name Format

The format of a name is defined by the naming service IDL (see OrbixNames Programmer's and Administrator's Guide for a complete listing of this IDL) in the module CosNaming2. Names are defined in IDL as follows:

#pragma prefix "omg.org"

module CosNaming {

    typedef string Istring;

    struct NameComponent {
      Istring id;
      Istring kind;
    };
    typedef sequence<NameComponent> Name;
    ...
    // Further definitions not shown...
};

An individual name component is defined as a struct containing two fields, an id field and a kind field. The id field is intended to function as a simple identifier while the kind field is intended to describe the purpose of the named entity. Note, however, that the kind field currently does not enjoy any special status. In most cases it is left blank (that is, equal to an empty string).

The id field and kind field taken together are used to specify a name component uniquely. Therefore, if the kind field is not blank it must also be supplied as part of the name component in order to avoid ambiguity.

A complete Name is defined to be a sequence of name components. For example, the name "example1.oracle" translates into a sequence of two name components:

Sequence index

id field

kind field

0

"example1"

""

1

"oracle"

""

The kind field is implicitly assumed to be blank.

In the string format used by the OrbixNames utilities, a '-' (hyphen) is used to separate the id field from the kind field. For example, a name such as "example1-TheKindField.oracle" translates into:

Sequence index

id field

kind field

0

"example1"

"TheKindField"

1

"oracle"

""

There are, in general, no restrictions on the strings specified in the id or kind fields except that OrbixNames will not accept an empty string for the id field.

Binding

The most common operation carried out by servers is that of publishing an object binding in the naming service. The entry thus created can be resolved by a client, at a later stage, in order to locate the associated object.

The operation used to create an object binding is called rebind() and it is defined as part of the interface NamingContext. Most of the interesting functionality of the naming service is defined in the interface NamingContext. The relevant extract of IDL is as follows:

#pragma prefix "omg.org"

module CosNaming {
    interface NamingContext {
      ...
      void rebind(in Name n, in Object obj)
        raises(NotFound, CannotProceed, InvalidName);
      ...
    };
    ...
    // Further definitions not shown...
};

The operation rebind() takes two arguments: a Name, as defined in the previous section, and Object, representing an object reference.

The type Object has a special significance in IDL. It represents the base interface for all interfaces--in other words, all IDL interfaces implicitly inherit from type Object. Therefore, a parameter of type Object allows you to pass object references of arbitrary type.

When rebind() is invoked it creates an object binding--an association between the specified name and object reference, stored persistently in the Names Repository. Since an object reference gives the location of a CORBA object, another way of expressing this is to say that the object's location is stored under a particular name.

There are, in fact, two operations available for creating object bindings: these are rebind() and bind(). The operation rebind() has more convenient semantics because it allows you to overwrite existing entries in the naming service.

Resolving

The most common operation carried out by clients is that of resolving a name to obtain an object reference. In doing so, clients are effectively executing a find operation because an object reference contains location details for the remote object.

The operation used to resolve a name is called resolve() and is defined as part of the NamingContext interface. The relevant extract of IDL is as follows:

#pragma prefix "omg.org"

module CosNaming {
    interface NamingContext {
      ...
      Object resolve(in Name n)
        raises(NotFound,
          CannotProceed,
          InvalidName);
      ...
    };
    ...
    // Further definitions not shown...
};

The client supplies the Name of the object it wishes to locate and receives a return value Object, which is the corresponding object reference.

The special type Object is also used here because it offers the flexibility to return object references of any type.

How Servers Bind Objects in OrbixNames

The following code sample demonstrates how a server names an object. It shows how the main() function of a StockWatch server could be changed to publish a single StockWatch object to the naming service.

// serverOracle.cc
    ...
    oracle_backend orac(argv[1], argv[2]);

    try {
        // Set my Orbix server name
        // It is recommended that this should be done BEFORE
        // objects are instantiated, and put into OrbixNames
  1.         CORBA::Orbix.setServerName(serverName);
            // Instantiate my instance of StockWatch
            StockWatch_i swatch(&orac);
            // Bind to OrbixNames
            CosNaming::NamingContext_var rootCtx;
    
  1.         rootCtx =
              CosNaming::NamingContext::_bind("root:NS",host);
            
            // Construct a Name. 
    
  2.         tmpName = new CosNaming::Name(2);
            tmpName->length(2);
            tmpName[0].id = CORBA::string_dup("example1");
            tmpName[0].kind = CORBA::string_dup("");
            tmpName[1].id = CORBA::string_dup("oracle");
            tmpName[1].kind = CORBA::string_dup("");
    
            // Now bind() my instance of StockWatch in OrbixNames
    
  3.         rootCtx->rebind(tmpName,&swatch);
              // Now wait for incoming invocations.
    
  4.         CORBA::Orbix.impl_is_ready(serverName,TIMEOUT);
        } catch ... // Series of catch blocks hidden 
    

The code is explained as follows:

  1. Initialize the Orbix specific server name to example1/oracle. Orbix requires that applications which publish objects in OrbixNames call either CORBA::BOA::setServerName() or CORBA::BOA::impl_is_ready() before interaction with OrbixNames. Object references cannot be correctly created until the server name has been set because Orbix needs to embed the server name into the object reference.
  2. Obtain a proxy to the root naming context using the Orbix _bind() feature. An alternative approach is to use IONA's implementation of the CORBA initialization service.
  3. Construct an instance of CosNaming::Name. The name constructed can be written in the OrbixNames utilities string format as "example1.oracle".
  4. Create a name-to-object binding in OrbixNames. The use of rebind() handles the case where the name-to-object mapping already exists in OrbixNames.
  5. A call to impl_is_ready() signals that the server is ready to receive invocations from clients and starts a CORBA event loop for the processing of these events.

How a Client Finds a Named Object

Clients find objects by resolving names and obtaining object references in the naming service. The following code sample illustrates how a C++ client finds a named object:

// clientns.cc

int main(int argc, char **argv) {
  StockWatch_var swatch;
  CosNaming::Name_var tmpName;

  char* server = argv[1];
  char* demo = argv[2]; 
  char* host = argv[3];

  try {
      // Bind to OrbixNames
      cout << " Binding to OrbixNames " << endl;
      CosNaming::NamingContext_var rootCtx;
  1.       rootCtx=CosNaming::NamingContext::_bind("root:NS",host);
          
          // Construct a Name
    
  1.       tmpName = new CosNaming::Name(2);
          tmpName->length(2);
          tmpName[0].id = CORBA::string_dup("example1");
          tmpName[0].kind = CORBA::string_dup("");
          tmpName[1].id = CORBA::string_dup(server);
          tmpName[1].kind = CORBA::string_dup("");
    
          // Now resolve() my instance of StockWatch in OrbixNames
    
  2.       CORBA::Object_var tmpObj = rootCtx->resolve(tmpName);
    
    
  3.       swatch = StockWatch::_narrow(tmpObj);
          if (CORBA::is_nil(swatch)) {
            cerr << "Error: StockWatch narrow failed" << endl;
            exit(1);
          }
        menuLoop(swatch);
        } catch ... // Series of catch blocks hidden
    }
    

The code is explained as follows:

  1. Obtain a proxy to the root naming context.
  2. Construct an instance of CosNaming::Name. The name constructed can be written in the OrbixNames utilities string format as "example1.oracle".
  3. Resolve the name in OrbixNames. If the binding exists, then an instance of CORBA::Object is returned, otherwise the exception CosNaming::NamingContext::NotFound is thrown.
  4. Narrow the instance of CORBA::Object to a StockWatch proxy. The type CORBA::Object is the C++ representation of the IDL type Object. Since CORBA::Object is a generic base class it is not possible to invoke any useful methods on the returned object reference until it has been narrowed to the correct type.

    In this example, a StockWatch object is expected so the static method StockWatch::_narrow() is used to cast tmpObj to the correct type.

    If the narrowing should fail for any reason, the _narrow() method will return a nil object reference. It will not throw a CORBA exception. To check for an error condition arising from a failed narrowing you can use the function CORBA::is_nil() to test if the narrowed reference (in this example swatch) is nil or not.

Names Wrapper Demo

One drawback to the NamingContext interface is that it does not support the specification of names in a simple string format. Instead the user is forced to specify a name in the relatively ungainly form of a CosNaming::Name, a sequence of structs.

To simplify the interface to the naming service, Orbix supplies a sample implementation of a parser which can convert a string format name to a CosNaming::Name. The parser is implemented by the class IT_Demo_NSWParser and can be found in the directory IONARoot/demos/demolib of your Orbix installation.

The public interface to the parser class is as follows:

// C++
// File: IT_Demo_NSWParser.h

class IT_Demo_NSWParser {
public:
  static IT_Demo_NSWParser *
  1.   create(
        char componentSeparator,
        char idKindSeparator,
        char escape
      ) throw ();
    
      virtual CosNaming::Name *
    
  1.   stringToName(
        const char *prefix,
        const char *name
      ) throw ();
    
      void
    
  2.   defineNameSeparators(
        char componentSeparator,
        char idKindSeparator,
        char escape
      ) throw ();
    
    };
    

The three public methods of IT_Demo_NSWParser can be explained as follows:

  1. The create() method is used instead of a constructor to create an instance of a parser object. For example, to create a parser that accepts strings in the same format as that used by the OrbixNames utilities you would initialize it as follows:
    // C++
    
    IT_Demo_NSWParser * theParserP;
    theParserP = create('.','-','\\');
    
  2. The method stringToName() carries out the conversion of a string to a CosNaming::Name. The prefix is simply prefixed to the name argument before the conversion takes place. That is, given the parser initialized as above, calling stringToName("example1","oracle") would be equivalent to calling stringToName("","example1.oracle").
  3. The method defineNameSeparators() allows the string format accepted by the parser instance to be altered at any time.

There is another class IT_Demo_NSW provided in the directory
IONARoot/demos/demolib, which provides a wrapper around the naming service. This wrapper class provides a slightly enhanced interface to the naming service and can be used instead of making invocations directly on the NamingContext interface. However, it must be borne in mind that this is not a standard interface and the code is supplied only as a demo.

The class IT_Demo_NSW has the following public methods:

class IT_Demo_NSW {
public:
  1.   IT_Demo_NSW () throw();
      virtual ~IT_Demo_NSW() throw();
    
    
  1.   virtual void setNamePrefix (const char *contextName) throw ();
    
    
  2.   virtual void clearNamePrefix () throw ();
    
    
  3.   virtual void registerObject (
        const char *objectName, 
        CORBA::Object_ptr object
      ) throw (CORBA::Exception);
    
      virtual CORBA::Object_ptr
    
  4.   resolveName(const char *name) throw (CORBA::Exception);
    
      virtual void 
    
  5.   removeObject (const char *objectName) throw (CORBA::Exception);
    
      enum BehaviourOption {
        ignoreNotFoundError = 0x01,
        createMissingContexts = 0x02,
        overwriteExistingObject = 0x04,
        deleteEmptyContexts = 0x08
      };
    
    
  6.   void setBehaviourOption(BehaviourOption option) throw ();
      ...
    };
    

The methods of class IT_Demo_NSW can be explained as follows:

  1. The constructor for IT_Demo_NSW initializes the wrapper in a state where it accepts names given in the string format of the OrbixNames utilities. In fact, each wrapper instance is implicitly associated with an instance of the parser IT_Demo_NSWParser which it uses to convert strings to names.
  2. The method setNamePrefix() specifies a name prefix which is prefixed to object names appearing in all subsequent invocations of registerObject(), resolveName() and removeObject().
  3. The method clearNamePrefix() clears the name prefix set by setNamePrefix().
  4. The method registerObject() replaces the functionality of rebind() or bind(), causing a name binding to be published to the naming service. It takes its name argument in a string format, prepending whatever name prefix is currently set. The semantics of this method are affected by the options set by setBehaviourOption().
  5. The method resolveObject() replaces the functionality of resolve(), looking up a name in the naming service and returning an object reference. It allows the name argument to be specified in a string format, prepending the current value of the name prefix.
  6. The method removeObject() replaces the functionality of unbind() and is used to delete a name binding from the naming service. The current name prefix is prepended to the objectName argument to specify the name binding. The semantics of this method are affected by the options set by setBehaviourOption().
  7. There are four behaviour options, of type BehaviourOption, which can be set via the method setBehaviourOptions(). These options have the following effects:

    ignoreNotFoundError--causes the removeObject() method to ignore any errors of the type CosNaming::NamingContext::NotFound.

    createMissingContexts-- registerObject() will create any naming contexts needed for the compound name.

    overwriteExistingObject--cause registerObject() to overwrite any existing entry in the naming service (same semantics as rebind()).

    deleteEmptyContexts--removeObject() will delete any contexts left empty as a result of removing an object from the naming service or as a result of the automatic removal of a context.

Server Code Using Names Wrapper

The following code extract shows the implementation of the main() function for the StockWatch server that connects to an Oracle database backend:

// C++
// File: serverOracle.cxx

...
#include <it_demo_nsw.h>
...

#define TIMEOUT 1*1000*60 // 1 mins.

int main(int argc, char **argv) {
  1.   CORBA::ORB_ptr orb = CORBA::ORB_init(argc,argv,"Orbix");
      CORBA::BOA_var boa = orb->BOA_init(argc, argv, "Orbix_BOA");
    
      char serverName[64];
      char *username   = argv[1];
      char *passwd     = argv[2];
    
      sprintf(serverName,"%s/%s","example1","oracle"); 
    
      orb->setServerName(serverName);
      //Indicate server should not quit while clients are connected
      boa->setNoHangup(1);
    
      // For correct generation of IORs, one must call impl_is_ready
      // before any objects are created.
    
  1.   boa->impl_is_ready(serverName, 0 );
    
      // Create an implementation object
      oracle_backend orac(username, passwd);
      
      try{
    
  2.     IT_Demo_NSW ns_wrapper;
        ns_wrapper.setNamePrefix("example1");
    
    
  3.     StockWatch_var swatch = new StockWatch_i(&orac);
    
        const char *object_name = "oracle";
    
    
  4.     ns_wrapper.setBehaviourOption(
                IT_Demo_NSW::createMissingContexts);
        ns_wrapper.setBehaviourOption(
                IT_Demo_NSW::overwriteExistingObject);
    
        // register the object in the naming service
    
  5.     ns_wrapper.registerObject(object_name,swatch);
    
    
  6.     boa->impl_is_ready(serverName, TIMEOUT);
      }
      catch (CORBA::Exception &ex) {
        cerr << ex << endl;
        CORBA::release(orb);
        exit(1);
      }
      cout << "server exiting" << endl;
    
      return 0;
    }
    

The code can be explained as follows:

  1. The functions CORBA::ORB_init() and CORBA::ORB::BOA_init() are used to obtain, respectively, references to a CORBA::ORB object and a CORBA::BOA object. These two functions are part of the CORBA initialization service used for bootstrapping an ORB. The two references orb and boa obtained in this way are meant to be used in place of the global static object CORBA::Orbix. The methods accessible from the CORBA::ORB object are useful for both clients and servers. The methods exposed by the CORBA::BOA class (which inherits from CORBA::ORB) are intended to be used by server programs. The use of the object CORBA::Orbix is now deprecated.
  2. The method impl_is_ready() is invoked with a zero timeout before any name bindings are published to the naming service. It must be invoked before any new object references are created. Note how it is invoked on the boa instance rather than using the CORBA::Orbix global object.
  3. An instance of a names wrapper IT_Demo_NSW is created and the prefix is set to be "example1".
  4. An instance of interface StockWatch is created by instantiating a StockWatch_i object.
  5. Two behaviour options are set for the names wrapper: createMissingContexts and overwriteExistingObject.
  6. The method registerObject() publishes the object reference swatch to the naming service. Assuming that object_name is equal to "oracle", the name used in the name binding is "example1.oracle" (since "example1" is the current prefix). On account of the behaviour options set in the preceding step, the naming context "example1" will be created if it does not already exist, and if the name binding "example1.oracle" already exists it will be overwritten.
  7. When impl_is_ready() is called with a non-zero timeout the server enters a state where it begins processing client connection requests and invocations.

Client Code Using Names Wrapper

The following code extract shows the implementation of the main() function for the client clientns.cxx which connects to the StockWatch server with the help of the naming service:

// C++
// File: clientns.cxx

...
#include <it_demo_nsw.h>
...

int main(int argc, char **argv) {
  char* object_name = argv[1];
  char* demo = argv[2]; 

  1.   CORBA::ORB_var orb = CORBA::ORB_init(argc,argv,"Orbix");
    
      try {
    
  1.     IT_Demo_NSW ns_wrapper;
        ns_wrapper.setNamePrefix(demo);
    
    
  2.     CORBA::Object_var obj = ns_wrapper.resolveName(object_name);
  3.     StockWatch_var swatch = StockWatch::_narrow(obj);
    
    
  4.     if ( CORBA::is_nil(swatch) ) {
          cout << "StockWatch::_narrow() failed" << endl;
          CORBA::release(orb);
          exit(1);
        }
        
        menuLoop(swatch);
      }
      catch (CORBA::Exception &ex) {
        cerr << ex << endl;
        CORBA::release(orb);
        exit(1);
      }
    
      return 0;
    }
    

The code can be explained as follows:

  1. The function CORBA::ORB_init() is used to obtain a reference, called orb above, to a CORBA::ORB object that is used on the client side. This function is part of the CORBA initialization service and replaces use of the now deprecated CORBA::Orbix object.
  2. An instance of a names wrapper IT_Demo_NSW is created and the prefix is set to the value of demo equal to the string "example1" in this case.
  3. The wrapper object is used to resolve a name given in string format. Assuming that object_name has been specified as "oracle" on the command line, and the prefix "example1" is in force, the name that is resolved is "example1.oracle".
  4. Narrow the instance of CORBA::Object to a StockWatch proxy. The type CORBA::Object is the C++ representation of the IDL type Object. Since CORBA::Object is a generic base class it is not possible to invoke any useful methods on the returned object reference until it has been narrowed to the correct type.

    In this example, a StockWatch object is expected so the static method StockWatch::_narrow() is used to cast obj to the correct type.

  5. If the narrowing should fail for any reason, the _narrow() method will return a nil object reference. It will not throw a CORBA exception. To check for an error condition arising from a failed narrow you must use CORBA::is_nil() to test whether the narrowed reference is nil or not.

Configuring OrbixNames

IONA's implementation of the naming service, OrbixNames, is written in Java making it possible to run this service on a wide variety of platforms. However, users do not have to worry much about Java configuration issues because the OrbixNames server is packaged in two different forms:

  1. OrbixNames is provided as a stand-alone executable ns. In this form, the Java runtime is combined with the executable and ns can be run in the same way as any binary executable.
  2. OrbixNames is also provided as a set of classes OrbixNames.jar and a Java runtime is provided in the IONARoot/tools/jre directory. This is the form of naming service that is normally used in conjunction with OrbixWeb.

In the following sections it is assumed that the various IONA products have been installed in:
/opt/iona UNIX default IONA root directory.
C:\Iona Windows default IONA root directory.

This IONA root directory will be referred to as IONARoot in the following subsections.

Configuration Variables for OrbixNames

The configuration variables for OrbixNames are usually found in the file IONARoot/config/orbixnames3.cfg. The following environment variables are the ones that most commonly need to be edited for OrbixNames, and they are found in the OrbixNames configuration scope:
IT_NAMES_SERVER The default server name for the naming service is NS. The server name specified here must be the same as that given in the putit command when registering the naming service.
IT_NAMES_SERVER_HOST The name of the host where the naming service will be run.
IT_NAMES_REPOSITORY_PATH The names repository is a file based repository for storing the persistent state of the naming service. This variable specifies the directory where the names repository is stored.

Two other configuration variables that are important for the naming service are Common.IT_DEFAULT_CLASSPATH and Common.IT_JAVA_INTERPRETER, from the file IONARoot/config/common.cfg. It should not be necessary to change these from the default install values. However, if the value of either of these variables is accidentally corrupted it can prevent the naming service from running.

Registering the Naming Service

Before the naming service can be used it must be registered with the Orbix daemon. The following steps assume that the naming service will be run on a host NSHost, which is just the value of OrbixNames.IT_NAMES_SERVER_HOST. The naming service can be registered as follows:

  1. Ensure that an Orbix daemon is running on NSHost. If necessary, start an Orbix daemon by typing the following at a command prompt:
      orbixd
    
  2. Register the naming service with the following command:

    Windows

      putit -h NSHost NS IONARoot\bin\ns
    

    UNIX

      putit -h NSHost NS IONARoot/bin/ns
    

    The option -h NSHost can be omitted if this command is executed on NSHost. Note that it is essential that the pathname given as the last argument is an absolute pathname.

    If you want to use the load balancing features of OrbixNames, you should register it using the following command instead:

    Windows

      putit -h NSHost NS "IONARoot\bin\ns -l"
    

    UNIX

      putit -h NSHost NS "IONARoot/bin/ns -l"
    

    The -l switch tells the naming service to enable load balancing. For further details, consult "Load Balancing" on page 187

  3. To test that the naming service has been correctly registered, enter:
      catit NS
    

    to view the registration details.

  4. Try running one of the naming service utilities:
      lsns -k
    

    This command lists the contents of the root context of the naming service. If this command is run against a newly configured naming service, you should see output like the following:

      [Contents of root]     lost+found- (Context)     ObjectGroups- (Context)   [0 Objects, 3 Contexts]

    If this runs successfully it means the naming service is correctly configured.

Naming Service Utilities

A number of command-line utilities are supplied with the naming service.

To list the contents of a particular naming context, use the following command:

lsns -k NamingContext

To create a new naming context in the naming hierarchy use the following command:

putnewncns NewNamingContext

To print the object reference (IOR) associated with a particular name binding, use the following command:

catns NameOfBinding

All of these commands take their name arguments in the string format discussed in the section "Name Format" on page 97.



1 This is not a standard string format. The OMG has yet to ratify a standard string format for names. However, according to the current draft of the Interoperable Naming Service, the name component separator character is likely to be standardized as '/' (forward slash).
2 The Cos in CosNaming stands for common object services.
support@iona.com
Copyright © 2000, IONA Technologies PLC.