A Policy-Driven CORBA Template Library to Facilitate the Rapid Development of DOC Middleware

A Policy-Driven CORBA Template Library to Facilitate the Rapid Development of DOC Middleware

By Jeff Mirwaisi

Overload, 11(57):, October 2003


While CORBA provides a robust, well-defined standard for the development of distributed object computing (DOC) middleware, the machinery needed to deploy a non-trivial application tends to be, at best, tedious and repetitive and, at worst, a source of hardto- discover errors. As an application/system grows in complexity, the need for a reusable CORBA machinery abstraction becomes self-evident. The tried-and-true practice of cutting and pasting and the common setup function paradigm does not provide an adequate solution to the problem. In order to address these needs and overcome the problems of the classical design, this paper presents a policy-driven abstraction mechanism that promotes code reuse while preserving the rich capabilities CORBA provides.

Introduction

CORBA has developed into a robust, mature distributed objectcomputing standard over the years, but the effort in environment setup of large systems with many servant types is problematic. The cost of recreating the same or slightly differing requirements for multiple servant types or for multiple applications is unacceptable given that the reward for such work is non-existent and the powerof modern development tools renders classical methods [ Henning- ] inefficient. Large-scale CORBA applications and systems may provide hundreds of disparate servant types with varying requirements on the underlying ORB facilities. To meet the needs of CORBA server/system developers, the following presents a C++ policy-driven library design that eliminates the code redundancy issues in traditional CORBA applications while maintaining the rich set of options provided by the CORBA standard.

Template meta-programming has emerged as a powerful tool with which to construct reusable, extensible libraries. The CORBA Template Library (CTL) relies heavily on said techniques and borrows the policy-driven design approach pioneered by Andrei Alexandrescu in "Modern C++ Design" (MCD) [ Alexandrescu ]. The generic functionality this provides is ideal for library designers as it allows an extensibility lacking in traditional OO- and procedure-based libraries. Template-based policies provide the necessary paradigm to accomplish the "write once use many" goal, while providing a method for future extension that is both safe and predictable, and allowing us to leverage working code to dramatically cut down on testing, development, and redesign time.

Motivation

Expertise in the application of CORBA and the design of distributed computing systems is secondary to that of constructing a system that "does something." Work involved in the design and development of the machinery needed to deploy an application using CORBA is wasted effort because the real objective is the functionality exposed via CORBA. For instance, in order to set up a minimal CORBA server environment, an ORB must be initialized, a servant must be instantiated and activated, and an active thread must run the ORB dispatching mechanism. In the simplest case, this is not a burden and does not indicate a need for yet another library/abstraction. A transient servant could simply be activated under the Root POA and theapplication's main thread would run the dispatch loop. If, however, we were to need a system with many servant instances, multiple POAs with differing POA policies and ORB requirements, the task becomes substantially more difficult. If we wish to develop a true peer-to-peer application, where theapplication is both a client and a server, instead of a pure server application, the necessary setup becomes even more cumbersome.

Historically, these concerns have been met with procedural setup and initialization functions, code repetition, or an OO abstraction mechanism, none of which are ideal. Procedural solutions fall short because of lack of easy reconfiguration of servant initializationbehavior. Environmental changes require modification of servant setup code, possibly in multiple locations, for instance, calls to a number of different POA setup procedures and object instantiation and activation functions. A simple change from a transient, systemidentified, stack-allocated servant to a persistent, user-identified, heap-allocated servant is error prone and takes more effort than the alternate policy-driven model presented here. The traditional OO-based design also fails to simplify deployment significantly and can in fact be more difficult to adapt than the procedural solution. Inheritance-based solutions rely on a given base type that limits your design choices or requires a large number of similar base types to achieve all possible combinations, i.e., a reference counted transient servant, a non reference counted version, and persistent version of both, etc.

Addressing these limitations with a reusable and extensible template library provides an abstraction layer that will allow developers to concentrate on the functionality they want to expose, not the machinery needed to expose them. To that end, the following presents some meta-programming techniques to simplify our goal, and a policy driven library of reusable types that simplify DOC middleware development to a point where the only new work needed in most cases is the development of the methods exposed by a given interface. The following pages present the basic techniques employed by the library and a synopsis of the library policies and their usage.

Basic CORBA Terminology

CORBA, Common Object Request Broker Architecture:

CORBA is a standardized middle-ware framework that facilitates distributed-object system development. The CORBA standard is maintained and developed by the OMG (Open Messaging Group, www.omg.org ) Among other things the standard provides specific mappings between a platform independent "interface definition language" (IDL) and a number of development languages such as C++ and Java, a transparent messaging infrastructure for marshalling requests for, and data to, the remote processes, and a set of well defined Services such as the Naming Service.

ORB, Object Request Broker:

the ORB is the messaging component of a CORBA system, all outgoing requests go through an ORB and are packaged and marshaled across the wire, all incoming requests are de-marshaled and dispatched by the ORB. The ORB has a number of secondary functions as well such as bootstrapping ( resolve_initial_references ), IOR management/manipulation ( string_to_object , object_to_string ) etc.

POA, Portable Object Adapter:

the POA is the bridge between the ORB and a servant skeleton. ORBs dispatch a remote request/invocation to a POA that in turn either rejects the message or delivers it to a concrete servant implementation by way of its skeleton.

CORBA Object:

Objects are the CORBA counterparts to servant instances, while the servant is a process local entity, an Object is accessible across process and machine boundaries.

IOR, Interoperable Object Reference:

IORs are encoded names that uniquely identify an Object, they include the host's contact information, the object key that identifies the Object to the ORB/POA, and any other data necessary to contact the Object.

IDL, Interface Definition Language:

IDL is a platform independent language to describe interfaces and data types. The IDL is run through an IDL compiler that generates language specific mappings of the types. Skeleton and stub code is generated by the IDL compiler.

RootPOA:

The Root POA is the primary POA hosted by an ORB; all other POAs are subordinates of the RootPOA. Simple transient Objects with system ids can be activated under the RootPOA. It is also the only POA immediately available when the ORB is initialized and the last to be destroyed during deactivation.

Name Service:

the Naming Service (CosNaming) is a CORBA service for exposing objects in a directory like manner - advertised object references are identified by a name and kind and placed in a naming-context, which is similar to a directory or folder. Clients can resolve names (files) and contexts (directories) bound to the Name Service obtaining an Object reference for the associated name that can be narrowed to an expected interface.

Skeleton:

generated server side code that bridges the gap between the POA and the servant, conforms to an expected interface used by the POA and provides the mapped IDL types interface to be overridden by the servant implementation

Stub:

generated client side code that supports the IDL specified interface and interacts with the ORB to reach a remote Object, a remote proxy to the Object and servant.

Servant:

user generated code that conforms to the interface expected by a skeleton and implements the necessary code to fulfill the methods of the IDL specified interface.

CORBA Template Library (CTL)

The CORBA Template Library (CTL) presented here attempts to provide an extensible library of policies and composable elements to simplify the deployment of large-scale CORBA applications. The design concentrates on the servant activation process, the mechanism by which the servant accesses the information it needs during the activation process, and the organization of servant instances in a larger service framework. The CTL is a freely available C++ source code library, available under the BSD license.

Currently the library is tied to ACE/TAO but work is underway to make it as ORB agnostic as possible.

Servant Activation/Deactivation

Servants represent the concrete implementation of a CORBA Object in a particular programming language. They are local instances of a type that are made known to the CORBA framework in order to service remote requests. A local servant instance is "activated," made known to the CORBA framework, by first instantiating the servant, finding or creating a suitable Portable Object Adapter (POA), registering with that POA, and finally some secondary object-activation activity such as Object exposition through the Naming Service. In order to preserve the rich set of options available to CORBA developers a policybased design was chosen that allows every set of possible activation scenarios to be expressed by way of four policy-type delegates: memory policies, POA- activation/selection policies, object-activation policies, and auxiliary policies. The servant activation ( createServant() ) and deactivation ( destroyServant() ) functions simply dispatch the requests to the specified policy delegates.

Memory Activation/Deactivation

The memory policy is the first called during activation and the last called during deactivation. Most users will simply use the empty memory policy, which does nothing. But a number of policies are provided to simplify garbage collection of free store allocated servants. The memory policy also provides a convenient base type for alternate allocation strategies, such as a pooled allocator.

POA Activation/Deactivation

The POA activation process is arguably the most cumbersome aspect of CORBA applications, and generates the most redundant code. While it is possible to use a procedural approach that simplifies the most common-use scenarios, with seven basic policies and a number of extension policies (pluggable protocols such as bi-directional GIOP) an exhaustive set of POA activation procedures is not a practical option.

The CTL provides two mechanisms by which a POA can be created: two functions create_poa and create_from_poa , which are explicitly specialized with the policy type desired; and a servant POA policy that will create the POA for a servant during the activation process. The two options allow for easy POA activation whether the POA to Object relationship is one-to-many or one-to-one.

A classic POA activation scenario involves first obtaining and narrowing a reference to the RootPOA obtaining the POAManager , generating the policy list, creating the new POA, and then cleaning up.

CORBA::Object_var obj
    = orb->resolve_initial_references("RootPOA");
PortableServer::POA_var root
    = PortableServer::POA::_narrow(obj);
PortableServer::POAManager_var poa_manager
    = root->the_POAManager();
CORBA::PolicyList policies;
policies.length(2);
policies[0] = root->create_lifespan_policy(
                       PortableServer::PERSISTENT);
policies[1] = root->create_id_assignment_policy(
                       PortableServer::USER_ID);
PortableServer::POA_var wfa_poa = root->create_POA(
                       "WidgetFactoryAdmin_i",
                       poa_manager,policies);
poa_manager->activate();
for(CORBA::ULong i=0;i<policies.length();++i)
  policies[i]->destroy();

The equivalent when using CTL is to specify a POA activation policy during servant activation:

POAPolicy::FromRoot<D,POAPolicy::PersistentUserID<> >
// POA policies which require an id to create the
// POA also provide a set_poa_id method.

And an example when using CTL to create a new POA outside of servant activation:

PortableServer::POA_var w_poa = POAPolicy::create_poa<
POAPolicy::PersistentUserID<> >(orb,"Widget_i");

The POA policy types provided are composable in a linear hierarchy, as are most of the types in the CTL. So for instance, assuming you wanted a POA that used bi-directional GIOP, hosted transient system id Objects, and didn't allow implicit activation of servants, the appropriate specification would merely be a string of nested template types:

POAPolicy::Bidirectional<
POAPolicy::TransientSystemID<
POAPolicy::NoImplicit<> > >

POA deactivation, in both the classic and CTL use scenarios, is relatively simple. The CTL POA policy takes care of it automatically if it created the POA. If the POA was not created during the activation process but was instead created as above the user is responsible for destroying the POA when it is no longer needed, for instance:

w_poa->destroy(true,true);

Object Activation/Deactivation

Once a suitable POA has been created a servant instance must be tied to the POA and an Object reference that specifies the servant to the outside world must be generated. A persistent Object reference remains the same across different execution contexts while a transient Object reference is unique each time the servant is activated. CTL provides two simple policy types that activate persistent and transient Objects under the specified POA during the activation process. A number of other policies are also provided such as Multicast servant activation but are not discussed here.

A classic persistent Object activation scenario:

PortableServer::ObjectId_var oid
    = PortableServer::string_to_ObjectId(
            "WidgetFactoryAdmin_i");
wfa_poa->activate_object_with_id(oid.in(),&wfa);
CORBA::Object_var wfa_obj
    = wfa_poa->id_to_reference((oid.in()));
Example::WidgetFactoryAdmin_var wfa_ref
    = Example::WidgetFactoryAdmin::_narrow(wfa_obj);

In contrast, when using the CTL, activating a servant with a persistent Object reference is simplified to specifying the object activation policy:

ObjPolicy::UserID<D>
// object policies which require an id during
// activation expose a set_object_id method.

Object deactivation is done automatically during servant deactivation in the case of CTL servants. A classic CORBA application would need to explicitly deactivate the Object when it is no longer needed:

PortableServer::ObjectId_var oid
    = wfa_poa->servant_to_id(&wfa);
wfa_poa->deactivate_object(oid);

Auxiliary Activation/Deactivation

Auxiliary policy processing is the last step during activation and the first during deactivation. It provides a point during the activation/deactivation process after the servant has been associated with a CORBA Object and before, in the case of deactivation, that Object has been torn down. As such it is an ideal point from which to expose the Object to some form of external lookup such as the Naming Service; or to set up messaging policies to be used, such as timeout policies. Two simple exposition policies are presented here: one that binds the newly activated Object to the ORB's IOR table, and one that binds the Object to the root naming-context of the Naming Service.

The IOR table provides a mechanism by which external ORB processes can discover an Object and obtain a reference to the Object. When a call to orb->resolve_initial_references("NameService") is made, if an initial reference was not supplied during ORB initialization a multicast request is sent to external ORB processes that in turn query their IOR table to try to fulfill the request for the name specified("NameService" in this case). A CTL servant can request IOR table binding and unbinding during activation and deactivation respectively, by providing the appropriate Auxiliary policy:

AUXPolicy::BindIORTable<D>
// The BindIORTable policy exposes a method
// set_iortable_id to specify the ID the Object
// reference will be associated with in the table.

The equivalent classic CORBA application would need to:

obj = orb->resolve_initial_references("IORTable");
IORTable::Table_var tbl
    = IORTable::Table::_narrow(obj);
CORBA::String_var str
    = orb1->object_to_string(wfa_obj);
tbl->rebind("WidgetFactoryAdmin_i",str.in());

in order to bind the Object reference to the IOR table. And in order to unbind do the complementary

tbl->unbind("WidgetFactoryAdmin_i");

during deactivation. This of course entails either maintaining the above IORTable::Table_var or reacquiring it when needed. The corresponding CTL use is transparent and automatically done during the servant deactivation process.

The Naming Service provides a directory service for exposing "well known" servants to distributed systems. Much like the IOR Table it provides a mechanism for exposition and lookup. CTL servants that wish to take advantage of the Naming Service simply specify the appropriate auxiliary policy such as:

AUXPolicy::BindRootNamingContext<D>
// The BindRootNamingContext policy provides a
// set_cosnaming_data method to specify the id and
// kind that will be bound to the root naming-context.

to be used during activation and deactivation. A classic CORBA application on the other hand needs to manually register with the Naming Service after the Object has been activated:

CosNaming::Name name;
name.length(1);
name[0].id
    = CORBA::string_dup("WidgetFactoryAdmin_i");
name[0].kind
    = CORBA::string_dup("WidgetFactoryAdmin_i");
obj = orb->resolve_initial_references("NameService");
CosNaming::NamingContext_var nc
    = CosNaming::NamingContext::_narrow(obj);
nc->rebind(name,wfa_obj);

And correspondingly unbind before deactivation:

nc->unbind(name);

Like the other CTL policy types the Auxiliary policies may be combined into a composite. So if a servant wanted to bind both to the IOR table and the Naming Service a composite policy type:

AUXPolicy<BindIORTable<D,
        AUXPolicy::BindRootNamingContext<D> >

could be used.

Mix-in Composition: Host and Delegate

The CTL makes extensive use of the Curiously Recurring Template Pattern and Parameterized Inheritance to deliver a "composable" design. Composable in this context refers to the ability to extend an inheritance hierarchy by nesting template types. The main use for this technique in the CTL is to provide a consistent means by which to retrieve the necessary data needed during servant activation/deactivation. The choice of whether to "host" the data in the servant type or to "delegate" the request to some other type is left up to the user. Hosts store the data as a member of the servant type and provide a common interface used by the policies to retrieve that data. Delegates provide the same interface but act as proxies for the data. The CTL provides a number of Host and Delegate mix-in types in the CTL::MIX::Host and CTL::MIX::Delegate namespaces respectively.

All the types in the MIX namespace use both a D (Derived) and B (Base) template parameter and are therefore "composable" as nested templates. Assuming S is the servant base type and we wished to construct a servant that hosts its own ORB_var (CTL::MIX::Host::ORB) and POA_var (CTL::MIX::Host::POA) we could form the composite type by writing out the template CTL::MIX::Host::ORB<D, CTL::MIX::Host::POA<D, S> > where D , as noted above, represents the most derived type i.e. the type that inherits from the composite. To simplify the above, which gets rather ugly when many types are composed, the CTL::Compose namespace provides a family of templates that will allow the type to be expressed in a simpler manner. For instance, the composeDB type could be use to rewrite the above as CTL::Compose::composeDB<D,S, CTL::MIX::Host::ORB, CTL::MIX::Host::POA>::type , or without the fully qualified names composeDB<D,S,ORB, POA>::type .

For a further discussion of composition see the sidebar Template Techniques Used in the CTL (next page), and the material in the references section. The following sections present some of the more important elements of the CTL::MIX namespace.

Mix-in ORB

Every servant needs a reference to an ORB so that a POA can either be found or created, and an Object can be activated. The ORB mix-in type provides an interface for accessing the ORB_var that the servant type will use. The Host variation maintains a member ORB_var and provides an additional method set_orb , while the Delegate dispatches the request to the servant's "container".

Mix-in ORBTask

CTL provides a helper type CTL::OrbTask that is simply an ORB_var and thread that runs the ORB reactor loop. The mix-in type CTL::MIX::Host::ORBTask helps simplify many multi-orb applications.

Mix-in POA

Once a POA has been created or found on behalf of a servant, it needs to be stored for subsequent use during Object activation and deactivation. The mix-in types CTL::MIX::Host::POA and CTL::MIX::Delegate::POA provide an interface that returns a PortableServer::POA_var& that the activation/deactivation policy methods can use to store and gain access to the associated POA. The Host variety also provides a set_poa method.

Servant Holders

Once a servant has been activated and an Object reference (IOR) has been generated that data needs to be stored somewhere. The CTL provides two simple holder types, CTL::BasicServant and CTL::TunneledServant . The only difference between the two is that CTL::TunneledServant allows the storage of a second IOR that identifies the servant, for instance an alternate route through a DSI gateway. Both Holder types support a getServantPtr and setServantPtr method. CTL::TunneledServant supports the additional methods getTServantPtr and setTServantPtr .

Container Holders

An optional Holder type that maintains a parameterized-backpointer to some arbitrary type is also provided. CTL::BasicContainer maintains this data and provides a simple interface for modifying and accessing the information. The Delegate types in CTL::MIX::Delegate use this information to redirect requests for activation data to the type specified to CTL::BasicContainer . The interface supported is very simple: getContainer , attachToContainer and detachFromContainer .

Servant State

CTL::ServantState acts as a common base that servants can use to report activation and deactivation failure. The interface is simple and merely provides a method of setting and querying a bit field that represents different servant states. All CTL servants support the CTL::ServantState interface.

A Complete Servant

All CTL servants provide a simple interface: createServant() and destroyServant() . These in turn use the policy types provided to activate and deactivate the servant. Though the interface is simple the templated nature of the policy driven design allows for an unlimited set of servant activation/deactivation scenarios. Two base servant types are presented below. They are identical except that the second inherits from an extra "container" template type that represents a container holder type as described in section 3.4. The number of template parameters may seem extravagant but they allow us to formulate a generic servant activation/deactivation mechanism.

template <typename D,typename S, typename SS,
          typename PP = CTL::POAPolicy::Empty,
          typename OP = CTL::ObjPolicy::Empty,
          typename MP = CTL::MemPolicy::Empty,
          typename AP = CTL::AUXPolicy::Empty,
          typename B = CTL::EmptyType >
struct CompositionServant : CTL::CompositionType<D,B>,
                    S,SS,MP,PP,OP,AP,CTL::ServantState {
  typedef B BASE;
  typedef D DERIVED;
  virtual void createServant() {
    initializeMemoryPolicy(
               static_cast<DERIVED*>(this));
    initializePOAPolicy(
               static_cast<DERIVED*>(this));
    initializeObjectPolicy(
               static_cast<DERIVED*>(this));
    initializeAUXPolicy(
               static_cast<DERIVED*>(this));
  }

  virtual void destroyServant() {
    destroyAUXPolicy(static_cast<DERIVED*>(this));
    destroyObjectPolicy(
               static_cast<DERIVED*>(this));
    destroyPOAPolicy(static_cast<DERIVED*>(this));
    destroyMemoryPolicy(
               static_cast<DERIVED*>(this));
  }
  virtual ~CompositionServant() {}
};

And the similar servant base type that also provides a template parameter for a container holder type.

Template Techniques used in the CTL

Fulfilling the requirements of an extensible, reusable library requires a number of template techniques. A basic understanding of template meta-programming is assumed, as is a familiarity with policy-driven design as expressed in "Modern C++ Design" (MCD) [ Alexandrescu ]. Beyond basic template techniques and the concepts propounded in MCD, a number of other mechanisms are used. The major techniques used in CTL are briefly described below.

Policy-based Design

Though the concept of policy driven designs is not completely new (POA policies), the concept has been adopted in recent years by the generic programming community, thanks in large part to the pioneering work presented in "Modern C++ Design" (MCD) and the Loki library. Policies represent the behavior exposed by a type ( P ) when dealing with a particular type ( T ) and, therefore, are groups of related functions, in contrast to traits, which describe a particular type ( T ) by way of a set of descriptive typedefs and values.

There are a number of ways to represent policies, the simplest of which is a class template that is made available to the client type via a template template type. Unfortunately, template template types are just now being handled consistently across different C++ compiler implementations. Therefore, CTL uses the simpler form of a specialized templated struct specialized by the client, except in the situation where the policy is not inherited. In those cases the policy design is simplified and a template type, whose methods are static, is expected. A complete discussion of policy-based metaprogramming is not given here, but for the sake of completeness the following gives a quick demonstration.

A simple example:

template <typename T>
struct P {void f() {}};

P is a very simple policy for an arbitrary type T . In the above example, P makes no demands on the type T . This is of course not the general case but is merely a simplification used to illustrate the policy paradigm.

template <template <typename U> class T>
struct C : T<C<T> > {};

C is an arbitrary client of policies. It designates that one arbitrary policy is needed (T) and will inherit from said policy, specifying itself as the parameter type.

typedef C<P> CP;

CP is a completely defined type, which uses the P policy above in conjunction with the C policy client.

A number of resources dealing with policies [ Alexandrescu ] and templates [ Vandevoorde- ] are provided in the references.

The Curiously Recurring Template Pattern

The Curiously Recurring Template Pattern (CRTP) [ Coplien ], for lack of a better term, is a template technique that introduces the most derived type as a parameter known to the base type at compile time. This fact allows the base type to make upcalls via a static cast of the instance to the derived type's complete interface. Effectively, this imposes interface requirements on the derived type. This is similar to an abstract interface, which needs to be overridden in the concrete type, without the necessity for a virtual function and the extra baggage of a formal abstract interface. Templates allow us to extend the interface concept by breaking away from the large-scale (whole interface) contract, to a method-by-method contract. The base type does not require that the derived type inherit from a particular base, only that it provides the methods we need.

A simplified example:

template <typename D>
struct U {
  void f() {static_cast<D*>(this)->g();}
};

U represents a template base type. It requires that derived types implement some function: <arbitrary return type> g() . Obviously U can require any signature for g . The simplest possible signature is required in the above example.

struct V : U<V> {void g() {}};

V represents a complete hierarchy. It implements the method as required by the base type ( U ) and inherits from a specialization of U , designating itself as the most derived type.

Parameterized Inheritance

Parameterized inheritance allows us another degree of freedom when creating a type. While traditional inheritance allows us to specify the access of the base type (public, protected, private), the number of copies of the base to keep (virtual), as well as whether a single or multiple base type(s) are the ancestor of the type, parameterized inheritance leverages the template system to inherit from an arbitrary type on demand. This allows us to reuse the same method implementation without regard to base type, thereby producing a true mix-in on-demand development strategy, which greatly enhances our ability to reuse code.

A simplified example:

template <typename B>
struct M : B {virtual void f() {}};

M represents a parameterized inheritance mix-in type, which provide an implementation of a method f() that can be used to make disparate abstract types concrete (assuming they only require an override of a pure method void f() ).

struct U {virtual void f()=0;};
struct V {virtual void f()=0;};

U and V represent two arbitrary unrelated abstract types with a common method ( void f() ). Derived types "could" either reproduce the common implementation in the concrete types, leading to code redundancy, or delegate to a non-member function common to both. Among other issues, this requires us to provide a delegating implementation in both concrete types (code redundancy) and, unless the common method is templated, either does not allow us to access state information encapsulated in the instance or forces us to inherit from a fixed base and use a polymorphic call from the common function being delegated to. Note that when combined with the above technique (CRTP), M 's implementation of f() can use state members and methods of the complete type.

struct CU : M<U> {};
struct CV : M<V> {};

CU and CV are concrete implementations of U and V , respectively. They are complete as a consequence of the shared implementation of f() implemented in M .

Parameterized Back-Pointer

Parameterized Back-Pointer formalizes a common methodology and abstracts it by using the generic meta-programming power of C++ templates. It is common practice to expect a member type to use a reference to the containing type.

struct S {
  struct T {
    T(S* s) : s_(s) {}
    S* s_;
  };
  S() : t_(this) {}
  T t_;
};

A similar scenario arises when we simply want a type to have a reference to an unrelated type that exposes an expected interface.

A simple example:

template <typename C>
struct CT {
  C* c_;
  CT(C* c) : c_(c) {}
  void g() {c_->f();}
};

CT represents a simple type that uses a parameterized backpointer. It expects a reference to some arbitrary type ( C ) that has a matching signature for f() . The relation between the parameter C and CT is not necessarily one of containment (i.e., CT is a member of C ) but we use the term containment in lieu of a better term.

struct U {
  void f() {}
  CT<U> ct_; U() : ct_(this) {}
};

U is an arbitrary type that supports the method f() as expected by CT , and contains an instance of CT (ct_) that is constructed with a pointer to the current U instance.

template <typename D, typename S, typename SS,
          typename PP = CTL::POAPolicy::Empty,
          typename OP = CTL::ObjPolicy::Empty,
          typename MP = CTL::MemPolicy::Empty,
          typename AP = CTL::AUXPolicy::Empty,
          typename C = CTL::EmptyType,
          typename B = CTL::EmptyType >
struct ContainedCompositionServant
             : CTL::CompositionServant<
                                D,S,SS,MP,PP,OP,AP,B>, C {
  typedef B BASE;
  typedef D DERIVED;
  virtual ~ContainedCompositionServant() {}
};

// The D parameter represents the Derived type.
// The S parameter represents a Servant holder type
//       i.e. BasicServant<Example::Widget>
// The SS parameter represents the skeleton type
//       i.e. POA_Example::Widget
// PP is the POA policy
// OP is the object-activation policy
// MP is the memory policy
// AP is the Auxiliary policy
// B is an arbitrary base type
// C, which is used in the second type, is the
//       Container Holder type.

Note that the resultant types inherit from all the template parameters and have their combined interface.

As shown above the CTL::ContainedCompositionServant type reuses the CTL::CompositionServant type. The CTL::CompositionType is not discussed here, but can be considered as B (the arbitrary base parameter).

To illustrate that the above is not as complicated as it seems from initial inspection, let's consider a simple servant. The servant will have a transient system id POA, and register with the Naming Service when activated.

module Example {
  interface MyType { string fn(); }
};

The types Example::MyType and POA_Example::MyType are generated from the above IDL. Below is a ready to use servant type and server. Note that the fully qualified names are dropped for readability.

struct simple : composeDB<
    simple, CompositionServant<
          simple,
          BasicServant<Example::MyType>,
          POA_Example::MyType, POAPolicy::FromRoot<
                POAPolicy::TransientSystemID<> >,
          ObjPolicy::SystemID<>, MemPolicy::Empty,
          AUXPolicy::BindRootNamingContext<
                simple> >,
    Host::ORB, Host::POA >::type {
  simple() {
    set_cosnaming_data("simple", "simple kind");
  }
  char* fn() {
    return CORBA::string_dup("Hello World");
  }
};

int main(int argc,char* argv[]) {
  simple s;
  s.orb_ = CORBA::ORB_init(argc,argv);
  s.createServant();
  s.orb_->run();
  return 0;
}

The above is a complete server application, simple::createServant() will construct the POA, activate the servant and register it with the Naming Service using the id "simple" and the kind "simple kind". If a client resolved the Object reference in the Naming Service and invoked the fn() method, the string "Hello World" would be returned.

A Widget Factory Example

To illustrate the power and ease of use of the CTL, the following presents an example that uses many of the CTL features. Experienced CORBA developers will note how much simpler the code is than a similar traditional application [ Henning- ]. An equivalent classic application can be found on the ACCU website. Beyond the exotic-looking base types, the code is almost reduced to that of a standard C++ application. Furthermore, if an error exists in the setup machinery, we have a single point of failure and only a single policy that needs to be debugged.

The example demonstrates a simple Widget server application. The server uses two ORBs, one for public access to the factory's interface and one that provides private access (from the local machine) to the factory's administrative interface. The two different servants that implement these interfaces share common information and are in fact part of a larger Widget Factory "component". Both the servants of the factory component are persistent Objects and each advertises its existence with the Naming Service and IOR table. The publicly accessible interface provides a method for Widget creation while the private admin interface provides a method to destroy all the Widgets created and a method to shut down the Widget Factory.

<example.idl>
module Example {
  interface Widget {
    void do_something();
  };
  interface WidgetFactory {
    Widget create_widget();
  };
  interface WidgetFactoryAdmin {
    void destroy_all_widgets();
    void shutdown_widget_factory();
  };

The above IDL describes the three basic interfaces and types that the following Widget factory will implement. Once it is run through an IDL compiler a skeleton and stub will be generated for Widget , WidgetFactory , and WidgetFactoryAdmin .

The servants presented here make use of a simple helper type CTL::RefCountImpl that extends the functionality of PortableServer::RefCountServantBase by providing methods to get the current reference count and to wait on a specific count.

template <typename D,typename S,
          typename SS,typename C>
struct factory_servant
     : ContainedCompositionServant<
            D,BasicServant<S>,
            SS, MemPolicy::Empty,
            POAPolicy::FromRoot<
                 D,POAPolicy::PersistantUserID<> >,
            ObjPolicy::UserID<D>,
            AUXPolicy::BindRootNamingContext<
                 D,AUXPolicy::BindIORTable<D> >,
            BasicContainer<C*>,
            RefCountImpl<
                 D,PortableServer
                       ::RefCountServantBase> > {};
template <typename D,typename S,
          typename SS,typename C>
struct simple_servant
     : ContainedCompositionServant<
            D,BasicServant<S>,
            SS, MemPolicy::Empty,
            POAPolicy::Empty,
            ObjPolicy::SystemID<D>,
            AUXPolicy::Empty,
            BasicContainer<C*>,
            RefCountImpl<
                 D,PortableServer
                       ::RefCountServantBase> > {};

These two servant base types will allow us to reuse the groups of policies without restating them for each servant type. The factory_servant base type will be shared between the WidgetFactory_i and WidgetFactoryAdmin_i types. The Widget_i type will use the base type simple_servant .

template <typename C>
struct Widget_I
     : composeDB<Widget_i<C>,
             simple_servant<Widget_i<C>,
                 Example::Widget,
                 POA_Example::Widget,C>,
             Delegate::ORB,Delegate::POA>::type {
  void do_something() throw (CORBA::Exception){}
};

Widgets are the product created by our factory for use by remote clients. Once Widget_i has been specialized for the container type it is a ready-to-use servant type. When createServant() is called the corresponding CORBA Object is activated and an Object reference is generated. WidgetFactoryAdmin_i will eventually call deactivateServant() for each of the Widget_i servants created by the factory.

template <typename C>
struct WidgetFactory_I
     : composeDB<WidgetFactory_i<C>,
           factory_servant<WidgetFactory_i<C>,
               Example::WidgetFactory,
               POA_Example::WidgetFactory, C>,
           Host::ORBTask,
           Host::POA >::type {
  WidgetFactory_i() {
    set_cosnaming_data("WidgetFactory_i",
                       "WidgetFactory_i");
    set_object_id("WidgetFactory_i");
    set_poa_id("WidgetFactory_i");
    set_iortable_id("WidgetFactory_i");
  }
  Example::Widget_ptr create_widget()
               throw (CORBA::SystemException) {
    ACE_Guard<ACE_Thread_Mutex>
                        grd(getContainer()->mtx_);
    Widget_i<C> * p = new Widget_i<C>;
                  //exception unsafe for clarity
    p->attachToContainer(getContainer());
    p->createServant();
    getContainer()->widget_data_.push_back(p);
    return p->getServantPtr();
  }
};

The WidgetFactory interface is the publicly visible portion of our component; any remote client can request a new Widget . WidgetFactory_i , a servant type that supports the WidgetFactory interface, is a template that generates a servant once it is specialized with a container type. The C (container) parameter will later be provided as WidgetFactoryData . Before the servant is activated, the user will provide a (C*) WidgetFactoryData* by way of attachToContainer , allowing us to gain access to the vector of Widget_i<WidgetFactoryData>* that is shared between the public factory interface's servant and the private factory admin interface's servant that is a member of WidgetFactoryData .

template <typename C>
struct WidgetFactoryAdmin_I
     : composeDB<WidgetFactoryAdmin_i<C>,
           factory_servant<
                WidgetFactoryAdmin_i<C>,
                Example::WidgetFactoryAdmin,
                POA_Example::WidgetFactoryAdmin,C>,
           Host::ORBTask,
           Host::POA >::type {
  WidgetFactoryAdmin_i() {
    set_cosnaming_data("WidgetFactoryAdmin_i",
                       "WidgetFactoryAdmin_i");
    set_object_id("WidgetFactoryAdmin_i");
    set_poa_id("WidgetFactoryAdmin_i");
    set_iortable_id("WidgetFactoryAdmin_i");
  }

  void destroy_all_widgets()
               throw (CORBA::SystemException) {
    ACE_Guard<ACE_Thread_Mutex>
               grd(getContainer()->mtx_);
    for(std::size_t i=0;
        i<getContainer()->widget_data_.size();
        ++i) {
      //simple loop for clarity
      getContainer()
             ->widget_data_[i]->destroyServant();
      getContainer()
             ->widget_data_[i]->_remove_ref();
    }
    getContainer()->widget_data_.clear();
  }

  void shutdown_widget_factory()
               throw (CORBA::SystemException) {
    getContainer()->event_.signal();
  }
};

WidgetFactoryAdmin_i is similar to the above WidgetFactory_i , except for the interface the servant will expose to remote clients. It also requires a template parameter to designate its "container" type, which will later be specified as WidgetFactoryData . Like the above servant type it also hosts its own CTL::OrbTask and POA. Because WidgetFactory_i and WidgetFactoryAdmin_i operate on independent ORBs a request coming in on WidgetFactory_i 's ORB can not gain access to the WidgetFactoryAdmin_i . Therefore if the ORB hosting the admin servant is listening on localhost:10000 only local access is granted to the admin interface.

struct WidgetFactoryData {
  ACE_Event event_;
  ACE_Thread_Mutex mtx_;
  PortableServer::POA_var widget_poa_;
  WidgetFactory_i<WidgetFactoryData> factory_;
  WidgetFactoryAdmin_i<WidgetFactoryData>
                             factory_admin_;
  std::vector<Widget_i<WidgetFactoryData>*>
                             widget_data_;
  // widget delegates to container to
  // determine orb to use
  CORBA::ORB_var& orb(Widget_i<
       WidgetFactoryData>*) {return factory_.orb_;}
  // widget delegates to container
  // to determine which POA to use
  PortableServer::POA_var& poa(Widget_i<
  WidgetFactoryData>*) {return widget_poa_;}
};

WidgetFactoryData is a "container" for all three servant-types. It provides the necessary interface for the Widget_i servant delegates, and specializes the WidgetFactory_i and WidgetFactoryAdmin_i templates with itself as the container type. Because Widget s are publicly available we reuse the public ORB specified by the WidgetFactory_i servant. WidgetFactoryData stores all the information that's shared across our "component".

int main(int argc,char* argv[]) {
  try {
    WidgetFactoryData data;
    data.factory_.orb_.initialize(
                           argc,argv,"public");
    data.factory_admin_.orb_.initialize(
                           argc,argv,"local");

Instantiate the "container" and initialize/activate the two independent ORBs

    data.factory_.attachToContainer(&data);
    data.factory_admin_.attachToContainer(&data);
    data.factory_.createServant();
    data.factory_admin_.createServant();

Attach the two factory servant types to the container and create the Objects, which will by virtue of the policies given automatically register themselves with the IOR table and the Naming Service.

    data.widget_poa_
      = POAPolicy::create_from_poa<
               POAPolicy::TransientSystemID<> >
               (data.factory_.orb_,
               data.factory_.poa_,
               "widget_poa");

Create the POA that Widget_Is will be activated in.

    data.event_.wait();

Wait for the WidgetFactoryAdmin_i servant to signal that the application should shut down. And then proceed to final cleanup.

    data.factory_admin_.destroy_all_widgets();

Destroy all Widget_i instances that were created on behalf of clients.

    data.widget_poa_->destroy(true,true);

Destroy the POA we manually created above

    data.factory_admin_.destroyServant();
    data.factory_.destroyServant();

Destroy the two factory servants

    data.factory_.orb_->destroy();
    data.factory_admin_.orb_->destroy ();

Finally, shutdown the two ORBs and their associated threads.

  }
  catch(const CORBA::Exception&) {return -1;}
  catch(...) {return -2;}
  return 0;
}

Conclusion

With the aid of a policy-driven CORBA template library, the deployment of the machinery needed to build large-scale distributed object computing facilities has been greatly simplified. Expertise about the CORBA setup mechanisms has been encapsulated in an extensible and easily applied framework of policies, thus allowing rapid development that concentrates on the application's functionality and lowers the barrier to entry for developers. Code reuse is promoted without relying on methods that are, at best, hard to extend (procedural, simple objectoriented inheritance or cut and paste). Therefore, the developer's task is reduced to that of providing the application's core functionality, freed of the burdens imposed by classic CORBA application designs. Easy extensibility ensures that future needs such as Real-Time ORB usage, SSLIOP setup, and Trading Service registration can be added once as policies and deployed with minimal effort.

References

[Henning-] Michi Henning, Steve Vinoski, Advanced CORBA Programming with C++ , 1999, Addison-Wesley

[Alexandrescu] Andrei Alexandrescu, Modern C++ Design , 2001, Addison Wesley

[Delaney] Mark Delaney, "Parameterized Inheritance Considered Helpful" CUJ , January 2003

[Vandevoorde-] David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide , 2002, Addison Wesley

[Coplien] Jim Coplien, "The Curiously Recurring Template Pattern", C++ Report , Feb. 1995, 24-27






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.