[Up: Examples]
[Previous: Examples] [Next: POA IDL]

Subsections


POA

The Basic Object Adapter provides a bare minimum of functionality to server applications. As a consequence, many ORBs added custom extensions to the BOA to support more complex demands upon an object adapter, making server implementations incompatible among different ORB vendors. In CORBA 2.2, the new Portable Object Adapter was introduced. It provides a much-extended interface that addresses many needs that were wished for, but not available with the original BOA specification. POA features include:

These features, make the POA much more powerful than the BOA and should fulfill most server applications' needs. As an example, object references for some million entries in a database can be generated, which are all implemented by a single default servant.

Example

As an example, let's write a simple POA-based server. You can find the full code in the demo/poa/hello-1 directory in the MICO distribution. Imagine a simple IDL description in the file ``hello.idl'':

  interface HelloWorld {
    void hello ();
  };

The first step is to invoke the IDL to C++ compiler in a way to produce skeleton classes that use the POA:

  idl --poa --no-boa hello.idl

The first option, -poa, turns on code generation for POA-based skeletons. The second option, -no-boa optionally turns off code generation for the old BOA-based skeletons. Next, we rewrite the server.

 1: // file server.cc
 2:
 3: #include "hello.h"
 4:
 5: class HelloWorld_impl : virtual public POA_HelloWorld
 6: {
 7:   public:
 8:     void hello() { printf ("Hello World!\n"); };
 9: };
10: 
11: 
12: int main( int argc, char *argv[] )
13: {
14:   CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "mico-local-orb");
15:   CORBA::Object_var poaobj = orb->resolve_initial_references ("RootPOA");
16:   PortableServer::POA_var poa = PortableServer::POA::_narrow (poaobj);
17:   PortableServer::POAManager_var mgr = poa->the_POAManager();
18:
19:   HelloWorld_impl * servant = new HelloWorld_impl;
20:
21:   PortableServer::ObjectId_var oid = poa->activate_object (servant);
22:
23:   mgr->activate ();
24:   orb->run();
25:
26:   poa->destroy (TRUE, TRUE);
27:   delete servant;
28:   return 0;
29: }

The object implementation does not change much with respect to a BOA-based one, the only difference is that HelloWorld_impl does not inherit from the BOA-based skeleton HelloWorld_skel any more, but from the POA-based skeleton POA_HelloWorld.

In main(), we first initialize the ORB, then we obtain a reference to the Root POA (lines 15-16) and to its POA Manager (line 17).

Then, we create an instance of our server object. In line 21, the servant is activated. Since the Root POA has the SYSTEM_ID policy, a unique Object Id is generated automatically and returned. At this point, clients can use the MICO binder to connect to the HelloWorld object.

However, client invocations upon the HelloWorld object are not yet processed. The Root POA's POA Manager is created in the holding state, so in line 23, we transition the POA Manager, and therefore the Root POA, to the active state. We then enter the ORB's event loop in 24.

In this example, run() never returns, because we don't provide a means to shut down the ORB. If that ever happened, lines 26-27 would first destroy the Root POA. Since that deactivates our active HelloWorld object, we can then safely delete the servant.

Since the Root POA has the IMPLICIT_ACTIVATION policy, we can also use several other methods to activate the servant instead of activate_object(). We could, for example, use servant_to_reference(), which first implicitly activates the inactive servant and then returns an object reference pointing to the servant. Or, we could invoke the servant's inherited _this() method, which also implicitly activates the servant and returns an object reference.

Using a Servant Manager

While the previous example did introduce the POA, it did not demonstrate any of its abilities - the example would have been just as simple using the BOA.

As a more complex example, we want to show a server that generates ``virtual'' object references that point to non-existent objects. We then provide the POA with a servant manager that incarnates the objects on demand.

We continue our series of ``Account'' examples. We provide the implementation for a Bank object with a single ``create'' operation that opens a new account. However, the Account object is not put into existence at that point, we just return a reference that will cause activation of an Account object when it is first accessed. This text will only show some code fragments; find the full code in the demo/poa/account-2 directory.

The implementation of the Account object does not differ from before. More interesting is the implementation of the Bank's create operation:

  Account_ptr
  Bank_impl::create ()
  {
    CORBA::Object_var obj = mypoa->create_reference ("IDL:Account:1.0");
    Account_ptr aref = Account::_narrow (obj);
    assert (!CORBA::is_nil (aref));
    return aref;
  }

The create_reference() operation on the POA does not cause an activation to take place. It only creates a new object reference encapsulating information about the supported interface and a unique (system-generated) Object Id. This reference is then returned to the client.

Now, when the client invokes an operation on the returned reference, the POA will first search its Active Object Map, but will find no servant to serve the request. We therefore implement a servant manager, which will be asked to find an appropriate implementation.

There are two types of servant managers: a Servant Activator activates a new servant, which will be retained in the POA's Active Object Map to serve further requests on the same object. A Servant Locator is used to locate a servant for a single invocation only; the servant will not be retained for future use. The type of servant manager depends on the POA's Servant Retention policy.

In our case, we use a servant activator, which will incarnate and activate a new servant whenever the account is used first. Further operations on the same object reference will use the already active servant. Since the create_reference() operation uses a unique Object Id each time it is called, one new servant will be incarnated for each Account.

A servant activator provides two operations, incarnate and etherealize. The former one is called when a new servant needs to be incarnated to serve a previously unknown Object Id. etherealize is called when the servant is deactivated (for example in POA shutdown) and allows the servant manager to clean up associated data.

  class AccountManager : public virtual POA_PortableServer::ServantActivator
  { /* declarations */ };

  PortableServer::Servant
  AccountManager::incarnate (/* params */)
  {
    return new Account_impl;
  }

  void
  AccountManager::etherealize (PortableServer::Servant serv,
                               /* many more params */)
  {
    delete serv;
  }

Our servant activator implements the POA_PortableServer::ServantActivator interface. Since servant managers are servants themselves, they must be activated like any other servant (see below).

The incarnate operation has nothing to do but to create a new Account servant. incarnate receives the current POA and the requested Object Id as parameters, so it would be possible to perform special initialization based on the Object Id that is to be served.

etherealize is just as simple, and deletes the servant. In ``real life'', the servant manager would have to make sure that the servant is not in use anywhere else before deleting it. Here, this is guaranteed by our program logic.

The main() code is a little more extensive than before. Because the Root POA has the USE_ACTIVE_OBJECT_MAP_ONLY policy and does not allow a servant manager, we must create our own POA with the USE_SERVANT_MANAGER policy.

  CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "mico-local-orb");
  CORBA::Object_var poaobj = orb->resolve_initial_references ("RootPOA");
  PortableServer::POA_var poa = PortableServer::POA::_narrow (poaobj);
  PortableServer::POAManager_var mgr = poa->the_POAManager();

  CORBA::PolicyList pl;
  pl.length(1);
  pl[0] = poa->
    create_request_processing_policy (PortableServer::USE_SERVANT_MANAGER);
  PortableServer::POA_var mypoa = poa->create_POA ("MyPOA", mgr, pl);

Note that we use the Root POA's POA Manager when creating the new POA. This means that the POA Manager has now control over both POAs, and changing its state affects both POAs. If we passed NULL as the second parameter to create_POA(), a new POA Manager would have been created, and we would have to change both POA's states separately.

We can now register the servant manager.

  AccountManager * am = new AccountManager;
  PortableServer::ServantManager_var amref = am->_this ();
  mypoa->set_servant_manager (amref);

After creating an instance of our servant manager, we obtain an object reference using the inherited _this() method. This also implicitly activates the servant manager in the Root POA.

  Bank_impl * micocash = new Bank_impl (mypoa);
  PortableServer::ObjectId_var oid = poa->activate_object (micocash);
  mgr->activate ();
  orb->run();

Now the only thing left to do is to activate a Bank object, to change both POAs to the active state, and to enter the ORB's event loop.

Persistent Objects

Our previous examples used ``transient'' objects which cannot outlive the server process they were created in. If you write a server that activates a servant and export its object reference, and then stop and re-start the server, clients will receive an exception that their object reference has become invalid.

In many cases it is desirable to have persistent objects. A persistent object has an infinite lifetime, not bound by the process that implements the object. You can kill and restart the server process, for example to save resources while it is not needed, or to update the implementation, and the client objects will not notice as long as the server is running whenever an invocation is performed.

An object is persistent if the servant that implements them is activated in a POA that has the PERSISTENT lifespan policy.

As an example, we will expand our Bank to create persistent accounts. When the server goes down, we want to write the account balances to a disk file, and when the server is restarted, the balances are read back in. To accomplish this, we use a persistent POA to create our accounts in. Using a servant manager provides us with the necessary hooks to save and restore the state: when etherealizing an account, the balance is written to disk, and when incarnating an account, we check if an appropriately named file with a balance exists.

We also make the Bank itself persistent, but use a different POA to activate the Bank in. Of course, we could use the Accounts' POA for the Bank, too, but then, our servant manager would have to discriminate whether it is etherealizing an Account or a Bank: using a different POA comes more cheaply.

The implementation of the Account object is the same as in the previous examples. The Bank is basically the same, too. One change is that the create operation has been extended to activate accounts with a specific Object Id - we will use an Account's Object Id as the name for the balance file on disk.

We also add a shutdown operation to the Bank interface, which is supposed to terminate the server process. This is accomplished simply by calling the ORB's shutdown method:

  void
  Bank_impl::shutdown (void)
  {
    orb->shutdown (TRUE);
  }

Invoking shutdown() on the ORB first of all causes the destruction of all object adapters. Destruction of the Account's POA next causes all active objects - our accounts - to be etherealized by invoking the servant manager. Consequently, the servant manager is all we need to save and restore our state.

One problem is that the servant manager's etherealize() method receives a PortableServer::Servant value. However, we need access to the implementation's type, Account_impl*, to query the current balance. Since CORBA does not provide narrowing for servant types, we have to find a solution on our own. Here, we use an STL map mapping the one to the other:

  class Account_impl;
  typedef map<PortableServer::Servant,
    Account_impl *,
    less<PortableServer::Servant> > ServantMap;
  ServantMap svmap;

When incarnating an account, we populate this map; when etherealizing the account, we can retrieve the implementation's pointer.

  PortableServer::Servant
  AccountManager::incarnate (/* params */)
  {
    Account_impl * account = new Account_impl;
    CORBA::Long amount = ...  // retrieve balance from disk
    account->deposit (amount);

    svmap[account] = account; // populate map
    return account;
  }

  void
  AccountManager::etherealize (PortableServer::Servant serv,
                               /* many more params */)
  {
    ServantMap::iterator it = svmap.find (serv);
    Account_impl * impl = (*it).second;
    ... // save balance to disk
    svmap.erase (it);
    delete serv;
  }

Please find the full source code in the demo/poa/account-3 directory.

One little bit of magic is left to do. Persistent POAs need a key, a unique ``implementation name'' to identify their objects with. This name must be given using the -POAImplName command line option:B.1

  ./server -POAImplName Bank

Now we have persistent objects, but still have to start up the server by hand. It would be much more convenient if the server was started automatically. This can be achieved using the MICO Daemon (micod) (see section 5.4).

For POA-based persistent servers, the implementation repository entry must use the ``poa'' activation mode, for example

  imr create Bank poa ./server IDL:Bank:1.0

The second parameter to imr, Bank, is the same implementation name as above; it must be unique within the implementation repository. If a persistent POA is in contact with the MICO Daemon, object references to a persistent object, when exported from the server process, will not point directly to the server but to the MICO Daemon. Whenever a request is received by micod, it checks if your server is running. If it is, the request is simply forwarded, else a new server is started.

Usually, the first instance of your server must be started manually for bootstrapping, so that you have a chance to export object references to your persistent objects. An alternative is to use the MICO Binder: the IDL:Bank:1.0 in the command line above tells micod that bind() requests for this repository id can be forwarded to this server - after starting it.

With POA-based persistent objects, you can also take advantage of the ``iioploc:'' addressing scheme that is introduced by the Interoperable Naming Service. Instead of using a stringified object reference, you can use a much simpler, URL-like scheme. The format for an iioploc address is

  iioploc://<host>:<port>/<object-key>

host and port are as given with the -ORBIIOPAddr command-line option, and the object key is composed of the implementation name, the POA name and the Object Id, separated by slashes. So, if you start a server using

  ./server -ORBIIOPAddr inet:thishost:1234 -POAImplName MyService

create a persistent POA with the name ``MyPOA'', and then activate an object using the ``MyObject'' Object Id, you could refer to that object using the IOR

  iioploc://thishost:1234/MyService/MyPOA/MyObject

These ``iioploc'' addresses are understood and translated by the string_to_object() method and can therefore be used wherever a stringified object reference can be used.

For added convenience, if the implementation name, the POA name and the Object Id are the same, they are collapsed into a single string. An example for this is the NameService implementation, which uses the ``NameService'' implementation name. The root naming context is then activated in the ``NameService'' POA using the ``NameService'' ObjectId. Consequently, the NameService can be addressed using

  iioploc://<host>:<port>/NameService

Please see the Interoperable Naming Service specification for more details.

Reference Counting

With the POA, implementations do not inherit from CORBA::Object. Consequently, memory management for servants is the user's responsibility. Eventually, a servant must be deleted with C++'s delete operator, and a user must know when a servant is safe to be deleted - deleting a servant that is still known to a POA leads to undesired results.

CORBA 2.3 addresses this problem and introduces reference counting for servants. However, to maintain compatibility, this feature is optional and must be explicitly activated by the user. This is done by adding POA_PortableServer::RefCountServantBase as a base class of your implementation:

class HelloWorld_impl :
  virtual public POA_HelloWorld
  virtual public PortableServer::RefCountServantBase
{
  ...
}

This activates two new operations for your implementation, _add_ref() and _remove_ref(). A newly constructed servant has a reference count of 1, and it is deleted automatically once its reference count drops to zero. This way, you can, for example, forget about your servant just after it has been created and activated:

  HelloWorld_impl * hw = new HelloWorld_impl;
  HelloWorld_var ref = hw->_this(); // implicit activation
  hw->_remove_ref ();

During activation, the POA has increased the reference count for the servant, so you can remove your reference immediately afterwards. The servant will be deleted automatically once the object is deactivated or the POA is destroyed. Note, however, that once you introduce reference counting, you must keep track of the references yourself: All POA operations that return a servant (i.e. id_to_servant() will increase the servants' reference count. The PortableServer::ServantBase_var class is provided for automated reference counting, acting the same as CORBA::Object_var does for Objects.


[Previous: Examples] [Next: POA IDL]
[Up: Examples]

Frank Pilhofer
1999-06-23