Copyright (C) 2000-2012 |
Whole document tree Chapter 2 The BasicsIn this chapter, we go through three examples to illustrate the practical steps to use omniORBpy. By going through the source code of each example, the essential concepts and APIs are introduced. If you have no previous experience with using CORBA, you should study this chapter in detail. There are pointers to other essential documents you should be familiar with.If you have experience with using other ORBs, you should still go through this chapter because it provides important information about the features and APIs that are necessarily omniORB specific. 2.1 The Echo exampleWe use an example which is similar to the one used in the omniORB manual. We define an interface, called Example::Echo, as follows:// echo_example.idl module Example { interface Echo { string echoString(in string mesg); }; };The important difference from the omniORB Echo example is that our Echo interface is declared within an IDL module named Example. The reason for this will become clear in a moment. If you are new to IDL, you can learn about its syntax in Chapter 3 of the CORBA specification 2.3 [OMG99]. For the moment, you only need to know that the interface consists of a single operation, echoString(), which takes a string as an argument and returns a copy of the same string. The interface is written in a file, called echo_example.idl. It is part of the CORBA standard that all IDL files should have the extension `.idl', although omniORB does not enforce this. 2.2 Generating the Python stubsFrom the IDL file, we use the IDL compiler, omniidl, to produce the Python stubs for that IDL. The stubs contain Python declarations for all the interfaces and types declared in the IDL, as required by the Python mapping. It is possible to generate stubs dynamically at run-time, as described in section 6.12, but it is more efficient to generate them statically.To generate the stubs, we use a command line like omniidl -bpython echo_example.idlAs required by the standard, that produces two Python packages derived from the module name Example. Directory Example contains the client-side definitions (and also the type declarations if there were any); directory Example__POA contains the server-side skeletons. This explains the difficulty with declarations at IDL global scope; section 2.7 explains how to access global declarations. If you look at the Python code in the two packages, you will see that they are almost empty. They simply import the echo_example_idl.py file, which is where both the client and server side declarations actually live. This arrangement is so that omniidl can easily extend the packages if other IDL files add declarations to the same IDL modules. 2.3 Object References and ServantsWe contact a CORBA object through an object reference. The actual implementation of a CORBA object is termed a servant.Object references and servants are quite separate entities, and it is important not to confuse the two. Client code deals purely with object references, so there can be no confusion; object implementation code must deal with both object references and servants. You will get a run-time error if you use a servant where an object reference is expected, or vice-versa. 2.4 Example 1 --- Colocated client and servantIn the first example, both the client and servant are in the same address space. The next sections show how the client and servant can be split between different address spaces.First, the code: 1 #!/usr/bin/env python 2 3 import sys 4 from omniORB import CORBA, PortableServer 5 import Example, Example__POA 6 7 class Echo_i (Example__POA.Echo): 8 def echoString(self, mesg): 9 print "echoString() called with message:", mesg 10 return mesg 11 12 orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) 13 poa = orb.resolve_initial_references("RootPOA") 14 15 ei = Echo_i() 16 eo = ei._this() 17 18 poaManager = poa._get_the_POAManager() 19 poaManager.activate() 20 21 message = "Hello" 22 result = eo.echoString(message) 23 24 print "I said '%s'. The object said '%s'." % (message,result)The example illustrates several important interactions among the ORB, the POA, the servant, and the client. Here are the details: 2.4.1 Imports
2.4.2 Servant class definition
2.4.3 ORB initialisation
2.4.4 Obtaining the Root POA
2.4.5 Object initialisation
2.4.6 Activating the POA
2.4.7 Performing a call
2.5 Example 2 --- Different Address SpacesIn this example, the client and the object implementation reside in two different address spaces. The code of this example is almost the same as the previous example. The only difference is the extra work which needs to be done to pass the object reference from the object implementation to the client.The simplest (and quite primitive) way to pass an object reference between two address spaces is to produce a stringified version of the object reference and to pass this string to the client as a command-line argument. The string is then converted by the client into a proper object reference. This method is used in this example. In the next example, we shall introduce a better way of passing the object reference using the CORBA Naming Service. 2.5.1 Object Implementation: Generating a Stringified Object Reference1 #!/usr/bin/env python 2 3 import sys 4 from omniORB import CORBA, PortableServer 5 import Example, Example__POA 6 7 class Echo_i (Example__POA.Echo): 8 def echoString(self, mesg): 9 print "echoString() called with message:", mesg 10 return mesg 11 12 orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) 13 poa = orb.resolve_initial_references("RootPOA") 14 15 ei = Echo_i() 16 eo = ei._this() 17 18 print orb.object_to_string(eo) 19 20 poaManager = poa._get_the_POAManager() 21 poaManager.activate() 22 23 orb.run()Up until line 18, this example is identical to the colocated case. On line 18, the ORB's object_to_string() operation is called. This results in a string starting with the signature `IOR:' and followed by some hexadecimal digits. All CORBA 2 compliant ORBs are able to convert the string into its internal representation of a so-called Interoperable Object Reference (IOR). The IOR contains the location information and a key to uniquely identify the object implementation in its own address space1. From the IOR, an object reference can be constructed. After the POA has been activated, orb.run() is called. Since omniORB is fully multi-threaded, it is not actually necessary to call orb.run() for operation dispatch to happen---if the main program had some other work to do, it could do so, and remote invocations would be dispatched in separate threads. However, in the absence of anything else to do, orb.run() is called so the thread blocks rather than exiting immediately when the end-of-file is reached. orb.run() stays blocked until the ORB is shut down. 2.5.2 Client: Using a Stringified Object Reference1 #!/usr/bin/env python 2 3 import sys 4 from omniORB import CORBA 5 import Example 6 7 orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) 8 9 ior = sys.argv[1] 10 obj = orb.string_to_object(ior) 11 12 eo = obj._narrow(Example.Echo) 13 14 if eo is None: 15 print "Object reference is not an Example::Echo" 16 sys.exit(1) 17 18 message = "Hello from Python" 19 result = eo.echoString(message) 20 21 print "I said '%s'. The object said '%s'." % (message,result)The stringified object reference is passed to the client as a command-line argument2. The client uses the ORB's string_to_object() function to convert the string into a generic object reference (CORBA.Object). On line 12, the object's _narrow() function is called to convert the CORBA.Object reference into an Example.Echo reference. If the IOR was not actually of type Example.Echo, or something derived from it, _narrow() returns None. In fact, since Python is a dynamically-typed language, string_to_object() is often able to return an object reference of a more derived type than CORBA.Object. See section 3.1 for details. 2.5.3 System exceptionsThe keep it short, the client code shown above performs no exception handling. A robust client (and server) should do, since there are a number of system exceptions which can arise.As already mentioned, ORB_init() can raise the CORBA.INITIALIZE exception if the command line arguments or configuration file are invalid. string_to_object() can raise two exceptions: if the string is not an IOR (or a valid URI with omniORB 3), it raises CORBA.BAD_PARAM; if the string looks like an IOR, but contains invalid data, is raises CORBA.MARSHAL. The call to echoString() can result in any of the CORBA system exceptions, since any exceptions not caught on the server side are propagated back to the client. Even if the implementation of echoString() does not raise any system exceptions itself, failures in invoking the operation can cause a number of exceptions. First, if the server process cannot be contacted, a CORBA.COMM_FAILURE exception is raised. Second, if the server process can be contacted, but the object in question does not exist there, a CORBA.OBJECT_NOT_EXIST exception is raised. Various events can also cause CORBA.TRANSIENT to be raised. If that occurs, omniORB's default behaviour is to automatically retry the invocation, with exponential back-off. As explained later in section 3.1, the call to _narrow() may also involve a call to the object to confirm its type. This means that _narrow() can also raise CORBA.COMM_FAILURE, CORBA.OBJECT_NOT_EXIST, and CORBA.TRANSIENT. Section 6.10 describes how exception handlers can be installed for all the various system exceptions, to avoid surrounding all code with try...except blocks. 2.5.4 Lifetime of a CORBA objectCORBA objects are either transient or persistent. The majority are transient, meaning that the lifetime of the CORBA object (as contacted through an object reference) is the same as the lifetime of its servant object. Persistent objects can live beyond the destruction of their servant object, the POA they were created in, and even their process. Persistent objects are, of course, only contactable when their associated servants are active, or can be activated by their POA with a servant manager3. A reference to a persistent object can be published, and will remain valid even if the server process is restarted.A POA's Lifespan Policy determines whether objects created within it are transient or persistent. The Root POA has the TRANSIENT policy. (Note that since only the Root POA is available when using omniORBpy with omniORB 2.8, it is not possible to create persistent objects in that environment.) An alternative to creating persistent objects is to register object references in a naming service and bind them to fixed pathnames. Clients can bind to the object implementations at runtime by asking the naming service to resolve the pathnames to the object references. CORBA defines a standard naming service, which is a component of the Common Object Services (COS) [OMG98], that can be used for this purpose. The next section describes an example of how to use the COS Naming Service. 2.6 Example 3 --- Using the Naming ServiceIn this example, the object implementation uses the Naming Service [OMG98] to pass on the object reference to the client. This method is far more practical than using stringified object references. The full listings of the server and client are below.The names used by the Naming service consist of a sequence of name components. Each name component has an id and a kind field, both of which are strings. All name components except the last one are bound to naming contexts. A naming context is analogous to a directory in a filing system: it can contain names of object references or other naming contexts. The last name component is bound to an object reference. Sequences of name components can be represented as a flat string, using `.' to separate the id and kind fields, and `/' to separate name components from each other4. In our example, the Echo object reference is bound to the stringified name `test.my_context/ExampleEcho.Object'. The kind field is intended to describe the name in a syntax-independent way. The naming service does not interpret, assign, or manage these values. However, both the name and the kind attribute must match for a name lookup to succeed. In this example, the kind values for test and ExampleEcho are chosen to be `my_context' and `Object' respectively. This is an arbitrary choice as there is no standardised set of kind values. 2.6.1 Obtaining the Root Context object referenceThe initial contact with the Naming Service can be established via the root context. The object reference to the root context is provided by the ORB and can be obtained by calling resolve_initial_references(). The following code fragment shows how it is used:import CosNaming orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) obj = orb.resolve_initial_references("NameService"); cxt = obj._narrow(CosNaming.NamingContext)Remember, omniORB constructs its internal list of initial references at initialisation time using the information provided in the configuration file omniORB.cfg, or given on the command line. If this file is not present, the internal list will be empty and resolve_initial_references() will raise a CORBA.ORB.InvalidName exception. Note that, like string_to_object(), resolve_initial_references() returns base CORBA.Object, so we should narrow it to the interface we want. In this case, we want CosNaming.NamingContext5. 2.6.2 The Naming Service interfaceIt is beyond the scope of this chapter to describe in detail the Naming Service interface. You should consult the CORBA services specification [OMG98] (chapter 3).2.6.3 Server codeHopefully, the server code is self-explanatory:#!/usr/bin/env python import sys from omniORB import CORBA, PortableServer import CosNaming, Example, Example__POA # Define an implementation of the Echo interface class Echo_i (Example__POA.Echo): def echoString(self, mesg): print "echoString() called with message:", mesg return mesg # Initialise the ORB and find the root POA orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) poa = orb.resolve_initial_references("RootPOA") # Create an instance of Echo_i and an Echo object reference ei = Echo_i() eo = ei._this() # Obtain a reference to the root naming context obj = orb.resolve_initial_references("NameService") rootContext = obj._narrow(CosNaming.NamingContext) if rootContext is None: print "Failed to narrow the root naming context" sys.exit(1) # Bind a context named "test.my_context" to the root context name = [CosNaming.NameComponent("test", "my_context")] try: testContext = rootContext.bind_new_context(name) print "New test context bound" except CosNaming.NamingContext.AlreadyBound, ex: print "Test context already exists" obj = rootContext.resolve(name) testContext = obj._narrow(CosNaming.NamingContext) if testContext is None: print "test.mycontext exists but is not a NamingContext" sys.exit(1) # Bind the Echo object to the test context name = [CosNaming.NameComponent("ExampleEcho", "Object")] try: testContext.bind(name, eo) print "New ExampleEcho object bound" except CosNaming.NamingContext.AlreadyBound: testContext.rebind(name, eo) print "ExampleEcho binding already existed -- rebound" # Activate the POA poaManager = poa._get_the_POAManager() poaManager.activate() # Block for ever (or until the ORB is shut down) orb.run() 2.6.4 Client codeHopefully the client code is self-explanatory too:#!/usr/bin/env python import sys from omniORB import CORBA import CosNaming, Example # Initialise the ORB orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) # Obtain a reference to the root naming context obj = orb.resolve_initial_references("NameService") rootContext = obj._narrow(CosNaming.NamingContext) if rootContext is None: print "Failed to narrow the root naming context" sys.exit(1) # Resolve the name "test.my_context/ExampleEcho.Object" name = [CosNaming.NameComponent("test", "my_context"), CosNaming.NameComponent("ExampleEcho", "Object")] try: obj = rootContext.resolve(name) except CosNaming.NamingContext.NotFound, ex: print "Name not found" sys.exit(1) # Narrow the object to an Example::Echo eo = obj._narrow(Example.Echo) if (eo is None): print "Object reference is not an Example::Echo" sys.exit(1) # Invoke the echoString operation message = "Hello from Python" result = eo.echoString(message) print "I said '%s'. The object said '%s'." % (message,result) 2.7 Global IDL definitionsAs we have seen, the Python mapping maps IDL modules to Python packages with the same name. This poses a problem for IDL declarations at global scope. Global declarations are generally a bad idea since they make name clashes more likely, but they must be supported.Since Python does not have a concept of a global scope (only a per-module global scope, which is dangerous to modify), global declarations are mapped to a specially named Python package. By default, this package is named _GlobalIDL, with skeletons in _GlobalIDL__POA. The package name may be changed with omniidl's -Wbglobal option, described in section 5.2. The omniORB C++ Echo example, with IDL: interface Echo { string echoString(in string mesg); };can therefore be supported with code like #!/usr/bin/env python import sys from omniORB import CORBA import _GlobalIDL orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) ior = sys.argv[1] obj = orb.string_to_object(ior) eo = obj._narrow(_GlobalIDL.Echo) message = "Hello from Python" result = eo.echoString(message) print "I said '%s'. The object said '%s'" % (message,result)
|