Previous Table of Contents Next


11.6.10 One Servant for All Objects


   By using the USE_DEFAULT_SERVANT policy, the developer can create a POA that will use a single servant to implement all of its objects. This approach is useful when there is very little data associated with each object, so little that the data can be encoded in the Object Id.

   The following example illustrates this approach by using a single servant to incarnate all CORBA objects that export a given interface in the context of a server. This example presumes a POA that has the USER_ID, NON_RETAIN, and USE_DEFAULT_SERVANT policies.

   Two interfaces are defined in IDL. The FileDescriptor interface is supported by objects that will encapsulate access to operations in a file associated with a file system. Global operations in a file system, such as the ones necessary to create FileDescriptor objects, are supported by objects that export the FileSystem interface.

   // IDL

   interface FileDescriptor { typedef sequence<octet> DataBuffer;

   long write (in DataBuffer buffer); DataBuffer read (

   in long num_bytes); void destroy (); };

   interface FileSystem { ... FileDescriptor open (

   in string file_name, in long flags); ... };

   Implementation of these two IDL interfaces may inherit from static skeleton classes generated by an IDL to C++ compiler as follows:

   // C++class FileDescriptorImpl : public POA_FileDescriptor{

   public:FileDescriptorImpl(POA_ptr poa);~FileDescriptorImpl();POA_ptr _default_POA();CORBA::Long write(

   const FileDescriptor::DataBuffer& buffer);FileDescriptor::DataBuffer* read(CORBA::Long num_bytes);void destroy();private:POA_ptr my_poa;};

   class FileSystemImpl : public POA_FileSystem{

   public:FileSystemImpl(POA_ptr poa);~FileSystemImpl();POA_ptr _default_POA();FileDescriptor_ptr open(

   const char* file_name, CORBA::Long flags);

   private:POA_ptr my_poa;FileDescriptorImpl* fd_servant;

   };

   A single servant may be used to serve all requests issued to all FileDescriptor objects created by a FileSystem object. The following fragment of code illustrates the steps to perform when a FileSystem servant is created.

   // C++FileSystemImpl::FileSystemImpl(POA_ptr poa): my_poa(POA::_duplicate(poa))

   {

   fd_servant = new FileDescriptorImpl(poa);

   poa->set_servant(fd_servant);};

   The following fragment of code illustrates how FileDescriptor objects are created as a result of invoking an operation (open) exported by a FileSystem object. First, a local file descriptor is created using the appropriate operating system call. Then a CORBA object reference is created and returned to the client. The value of the local file descriptor will be used to distinguish the new FileDescriptor object from other FileDescriptor objects. Note that FileDescriptor objects in the example are transient, since they use the value of their file descriptors for their ObjectIds, and of course the file descriptors are only valid for the life of a process.

   // C++FileDescriptor_ptrFileSystemImpl::open(

   const char* file_name, CORBA::Long flags){

   int fd = ::open(file_name, flags);

   ostrstream ostr;

   ostr << fd;

   PortableServer::ObjectId_var oid =

   PortableServer::string_to_ObjectId(ostr.str());

   Object_var obj = my_poa->create_reference_with_id(

    oid.in(),"IDL:FileDescriptor:1.0");return FileDescriptor::_narrow(obj);};

   Any request issued to a FileDescriptor object is handled by the same servant. In the context of a method invocation, the servant determines which particular object is being incarnated by invoking an operation that returns a reference to the target object and, after that, invoking POA::reference_to_id. In C++, the operation used to obtain a reference to the target object is _this(). Typically, the ObjectId value associated with the reference will be used to retrieve the state of the target object. However, in this example, such a step is not required since the only thing that is needed is the value for the local file descriptor and that value coincides with the ObjectId value associated with the reference.

   Implementation of the read operation is rather simple. The servant determines which object it is incarnating, obtains the local file descriptor matching its identity, performs the appropriate operating system call, and returns the result in a DataBuffer sequence.

   // C++FileDescriptor::DataBuffer*FileDescriptorImpl::read(CORBA::Long num_bytes){

   FileDescriptor_var me = _this();

   PortableServer::ObjectId_var oid =

   my_poa->reference_to_id(me.in());

   CORBA::String_var s =

   PortableServer::ObjectId_to_string(oid.in());istrstream is(s);int fd;is >> fd;CORBA::Octet* buffer = DataBuffer::alloc_buf(num_bytes);int len = ::read(fd, buffer, num_bytes);DataBuffer* result = new DataBuffer(len, len, buffer, 1);return result;

   };

   Using a single servant per interface is useful in at least two situations.

   11.6.11 Single Servant, Many Objects and Types, Using DSI

   The ability to associate a single DSI servant with many CORBA objects is rather powerful in some scenarios. It can be the basis for development of gateways to legacy systems or software that mediates with external hardware, for example.

   Usage of the DSI is illustrated in the following example. This example presumes a POA that supports the USER_ID, USE_DEFAULT_SERVANT, and RETAIN policies.

   A single servant will be used to incarnate a huge number of CORBA objects, each of them representing a separate entry in a Database. There may be several types of entries in the Database, representing different entity types in the Database model. Each type of entry in the Database is associated with a separate interface that comprises operations supported by the Database on entries of that type. All these interfaces inherit from the DatabaseEntry interface. Finally, an object supporting the DatabaseAgent interface supports basic operations in the database such as creating a new entry, destroying an existing entry, etc.

   // IDLinterface DatabaseEntry {readonly attribute string name;};

   interface Employee : DatabaseEntry { attribute long id; attribute long salary;

   }; ...

   interface DatabaseAgent {

   DatabaseEntry create_entry (in string key, in CORBA::Identifier entry_type,in NVPairSequence initial_attribute_values

   );

   void destroy_entry (in string key);...

   };

   Implementation of the DatabaseEntry interface may inherit from the standard dynamic skeleton class as follows:

   // C++class DatabaseEntryImpl : public PortableServer::DynamicImplementation {

   public:DatabaseEntryImpl (DatabaseAccessPoint db);virtual void invoke (ServerRequest_ptr request);~DatabaseEntryImpl ();

   virtual POA_ptr _default_POA(){return poa;}};

   On the other hand, implementation of the DatabaseAgent interface may inherit from a static skeleton class generated by an IDL to C++ compiler as follows:

   // C++class DatabaseAgentImpl : public DatabaseAgentImplBase{

   protected:DatabaseAccessPoint mydb;DatabaseEntryImpl * common_servant;

   public:DatabaseAgentImpl ();virtual DatabaseEntry_ptr create_entry (

   const char * key, const char * entry_type,const NVPairSequence& initial_attribute_values

   );virtual void destroy_entry (const char * key);

   ~DatabaseAgentImpl ();};

   A single servant may be used to serve all requests issued to all DatabaseEntry objects created by a DatabaseAgent object. The following fragment of code illustrates the steps to perform when a DatabaseAgent servant is created. First, access to the database is initialized. As a result, some kind of descriptor (a DatabaseAccessPoint local object) used to operate on the database is obtained. Finally, a servant will be created and associated with the POA.

   // C++void DatabaseAgentImpl::DatabaseAgentImpl (){

   mydb = ...;common_servant = new DatabaseEntryImpl(mydb);poa->set_servant(common_servant);

   };

   The code used to create DatabaseEntry objects representing entries in the database is similar to the one used for creating FileDescriptor objects in the example of the previous section. In this case, a new entry is created in the database and the key associated with that entry will be used to represent the identity for the corresponding DatabaseEntry object. All requests issued to a DatabaseEntry object are handled by the same servant because references to this type of object are associated with a common POA created with the USE_DEFAULT_SERVANT policy.

   // C++

   DatabaseEntry_ptr DatabaseAgentImpl::create_entry (const char * key, const char * entry_type,const NVPairSequence& initial_attribute_values)

   // creates a new entry in the database:mydb->new_entry (key, ...);

   // creates a reference to the CORBA object used to // encapsulate access to the new entry in the database.// There is an interface for each entry type:CORBA::Object_ptr obj = poa->create_reference_with_id(

   string_to_ObjectId (key),identifierToRepositoryId (entry_type),);

   DatabaseEntry_ptr entry = DatabaseEntry::_narrow (obj);CORBA::release (obj);return entry;};

   Any request issued to a DatabaseEntry object is handled by the same servant. In the context of a method invocation, the servant determines which particular object it is incarnating, obtains the database key matching its identity, invokes the appropriate operation in the database and returns the result as an output parameter in the ServerRequest object.

   Sometimes, a program may need to determine the type of an entry in the database in order to invoke operations on the entry. If that is the case, the servant may obtain the type of an entry based on the interface supported by the DatabaseEntry object encapsulating access to that entry. This interface may be obtained by means of invoking the get_interface operation exported by the reference to the DatabaseEntry object.

   // C++void DatabaseEntryImpl::invoke (ServerRequest_ptr request){

   CORBA::Object_ptr current_obj = _this ();

   // The servant determines the key associated with

   // the database entry represented by current_obj:

   PortableServer::ObjectId oid =

   poa->reference_to_id (current_obj);

   char * key = ObjectId_to_string (oid);

   // The servant handles the incoming CORBA request. This

   // typically involves the following steps:

   // 1. mapping the CORBA request into a database request

   // using the key obtained previously

   // 2. constructing output parameters to the CORBA request

   // from the response to the database request

   ...};

   Note that in this example, we may have a billion DatabaseEntry objects in a server requiring only a single entry in map tables supported by the POA (that is, the ORB at the server). No permanent storage is required for references to DatabaseEntry objects at the server. Actually, references to DatabaseEntry objects will only occupy space:

   Scalability problems can be handled using this technique. There are many scenarios where this scalability causes no penalty in terms of performance (basically, when there is no need to restore the state of an object, each time a request to it is being served).