![]() |
![]() |
![]() |
![]() |
Introduction to Orbix C++ Edition |
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 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
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.
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:
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:
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.
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.
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.
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
CosNaming::NamingContext::_bind("root:NS",host); // Construct a Name.
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
// Now wait for incoming invocations.
} catch ... // Series of catch blocks hidden
The code is explained as follows:
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.
root naming context using the Orbix _bind() feature. An alternative approach is to use IONA's implementation of the CORBA initialization service.
CosNaming::Name. The name constructed can be written in the OrbixNames utilities string format as "example1.oracle".
rebind() handles the case where the name-to-object mapping already exists in OrbixNames.
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.
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;
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
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:
root naming context.
CosNaming::Name. The name constructed can be written in the OrbixNames utilities string format as "example1.oracle".
CORBA::Object is returned, otherwise the exception CosNaming::NamingContext::NotFound is thrown.
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.
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 *
const char *prefix, const char *name ) throw (); void
char componentSeparator, char idKindSeparator, char escape ) throw (); };
The three public methods of IT_Demo_NSWParser can be explained as follows:
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('.','-','\\');
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").
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:
const char *objectName, CORBA::Object_ptr object ) throw (CORBA::Exception); virtual CORBA::Object_ptr
virtual void
enum BehaviourOption { ignoreNotFoundError = 0x01, createMissingContexts = 0x02, overwriteExistingObject = 0x04, deleteEmptyContexts = 0x08 };
... };
The methods of class IT_Demo_NSW can be explained as follows:
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.
setNamePrefix() specifies a name prefix which is prefixed to object names appearing in all subsequent invocations of registerObject(), resolveName() and removeObject().
clearNamePrefix() clears the name prefix set by setNamePrefix().
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().
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.
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().
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.
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) {
- 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.
// Create an implementation object oracle_backend orac(username, passwd); try{
ns_wrapper.setNamePrefix("example1");
const char *object_name = "oracle";
IT_Demo_NSW::createMissingContexts); ns_wrapper.setBehaviourOption( IT_Demo_NSW::overwriteExistingObject); // register the object in the naming service
} 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:
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.
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.
IT_Demo_NSW is created and the prefix is set to be "example1".
StockWatch is created by instantiating a StockWatch_i object.
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.
impl_is_ready() is called with a non-zero timeout the server enters a state where it begins processing client connection requests and invocations.
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];
ns_wrapper.setNamePrefix(demo);
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:
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.
IT_Demo_NSW is created and the prefix is set to the value of demo equal to the string "example1" in this case.
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".
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.
_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.
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:
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.
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.
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:
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.
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:
orbixd
putit -h NSHost NS IONARoot\bin\ns
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:
putit -h NSHost NS "IONARoot\bin\ns -l"
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
catit NS
to view the registration details.
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.
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.
|
support@iona.com Copyright © 2000, IONA Technologies PLC. |