The CORBA Component Model Part 4: Implementing Components with CCM

Our previous columns presented the design of our stock quoter example using the CORBA Component Model (CCM) and described how the CCM container architecture and Component Implementation Framework (CIF) are designed. This column deepens our coverage of CCM by showing how the CIF can be used to implement the components in our stock quoter application using C++ and CCM and interact with component containers, which provide the runtime environment for one or more component implementations called executors, which in turn are where components are actually implemented by CCM server application developers.


October 01, 2004
URL:http://www.drdobbs.com/the-corba-component-model-part-4-impleme/184403889

Introduction

Our previous columns [Schmidt04b, Schmidt04c] presented the design of our stock quoter example using the CORBA Component Model (CCM) [CORBA3] and described how the CCM container architecture and Component Implementation Framework (CIF) are designed. This column deepens our coverage of CCM by showing how the CIF can be used to implement the components in our stock quoter application using C++ and CCM and interact with component containers, which provide the runtime environment for one or more component implementations called executors, which in turn are where components are actually implemented by CCM server application developers. Figure 1 illustrates the components in our stock quoter system example. The StockDistributor component monitors a real-time stock database. When the values of particular stocks change, it pushes a CCM eventtype that contains the stock's name via a CCM event source to the corresponding CCM event sink implemented by one or more StockBroker components. If these components are interested in the stock they can obtain more information about it by invoking a request/response operation via their CCM receptacle on a CCM facet exported by the StockDistributor component.


Figure 1: CCM Architecture of the Stock Quoter System

The remainder of this column shows how to implement the stock quoter application shown in Figure 1, focusing on how to implement the StockBroker and StockDistributor components using CCM features. In particular, the CCM container framework and CIF described in our previous column [Schmidt04c] provide the following important capabilities to developers of the stock quoter system:

Implementing Components

This section shows how to use CCM containers and the CIF to implement the components in our stock quoter example. The example is illustrated in Figure 1, and is implemented using the following steps:

  1. Create component implementation definitions by first defining components and their homes using IDL3.x keywords and then using the Component Implementation Definition Language (CIDL) to describe the structure and state of the component implementations, which is referred to as the composition of a component.
  2. Compile the component implementation definitions by running them through a CIDL compiler, which generates implementation skeleton "glue" code (e.g., servants and executor interfaces) that reduces the amount of software written by server application developers.
  3. Implement component executors, which ultimately perform the business logic requested by clients.

We discuss each of these steps in greater detail below.

Step 1: Create Component Implementation Definitions

As with the distributed object computing programming techniques supported in CORBA 2.x, application developers must first define interfaces for components that need to be developed and deployed. We start by defining our components using IDL3.x keywords (such as component, home, provides, uses, publishes, emits, and consumes), as illustrated in [Schmidt04b]. We also use IDL 2.x keywords (such as struct, sequence, long, Object, interface, raises, etc.) to complete the definition.

Component developers need to define the structure and state of their components. The CCM approach is to use the composition declaration in CIDL that describes how to connect component definitions (which perform business logic) with home definitions (which are the factories that create components). Since a component can be instantiated by more than one home, compositions designate which home will manage which component. The general form of a CIDL composition is shown in Figure 2.


Figure 2: Overview of a CIDL Composition

This figure illustrates the relationships between the following entities defined in a composition:

Both Figure 2 and the explanation above reinforce that the focus of compositions is the home rather than the component. This design is essentially the Factory Method pattern [GoF], which localizes the creation of an object in a subclass. In CCM, the home is the primary point of contact for a client, and the home's interface and behavior impact the interaction between clients and the component.

CCM allows developers to define a single component and have multiple home definitions that manage the same component using IDL3.x keywords. By defining the composition, the component developer designates the relationship between a home and the component it manages. It is important to note, however, that the composition does not specify component types explicitly, but rather implicitly from the home type within the composition since each home can manage only one type of component.

The CIDL for our stock quoter example is placed in a file called stock.cdl, as shown below:

composition session StockDistributor_Impl {
       home executor StockDistributorHome_Exec  {
               implements StockDistributorHome;      
               manages StockDistributor_Exec;  
       }
};
composition session StockBroker_Impl {
       home executor StockBrokerHome_Exec  {
               implements StockBrokerHome;      
               manages StockBroker_Exec;  
       }
};

These two compositions convey the following information to a CIDL compiler:

The compositions described above show the mandatory parts of a composition specification. CIDL also allows server application developers to define the following optional specifications:

  1. Storage specifications, which provide ways to associate state information with the components. The binding of the state information with the components occurs through the uses catalog specification within the composition definition. The composition will refer to a persistent state definition language [PSDL] definition for associating state information.
  2. Home operation delegation specification, which maps operations defined on the component home to operations on either the abstract storage home or the component executor. The CIDL uses this description to generate implementations of operations on the home executor, and to generate operation declarations on the component executor. Operation delegations can be specified explicitly or implicitly in the composition.
  3. Proxy home, which implements the home interface for the component, but is not required to be collocated with the container that executes the component implementation managed by the home. In some configurations, proxy homes can provide implementations of home operations without contacting the container that executes the actual home and component implementation. The use of proxy homes is transparent to clients and largely transparent to component implementations. The runtime component of CIF is expected to maintain the relation between the proxy homes and the actual homes that they represent.
  4. Segmented executors, which are a set of distinct artifacts from the containers perspective, as opposed to monolithic executors, which are treated as a single entity by the container. The component executor in our stock.cdl file is an example of monolithic executors. Each segment in a segmented executor may have distinct abstract state declarations. The motivation is to allow a segmented executor with at least one facet to be activated individually, instead of activating the whole component.

We omit these optional composition features in our example since we don't need them to implement our stock broker application.

Step 2: Compile the Component Implementation Definitions to Generate Glue Code

Component applications that implement the business logic are developed by extending the language-specific interfaces generated from the composition definitions created in Step 1 above. These interfaces are generated by compiling the CIDL files using a CCM-compliant CIDL compiler. In addition to generating C++ code and IDL interfaces that are needed for component implementations, a CIDL compiler also generates a considerable amount of glue code that is used for component configuration and deployment (note that a CCM-compliant CIDL implementation can generate Java or C++ code instead of IDL). In CORBA 2.x applications, application developers had to manually write nontrivial amounts of this glue code themselves, which is tedious and error prone. In CCM, much of this code is generated automatically and manipulated by various compilers and tools, which helps separate concerns and reduce development effort.

In our stock quoter example, when the stock.cdl file is passed to the CIAO CIDL compiler (http://www.dre.vanderbilt.edu/CIAO), it generates the following files shown in Figure 3 and described below. The names of the files and contents are based on the CIAO CCM implementation and are not standardized. They have been shown to enhance your understanding of the generated code and subsequent C++ code examples.


Figure 3: Files Generated by CIAO's CIDL Compiler for stock.cdl

Step 3: Implement Component and Home Executors

Up to this point, we have focused on the various CIDL-generated definitions that are required to bootstrap the CCM component server infrastructure. In this third step, we describe how to implement executors for the StockBroker and StockDistributor components and the homes that create them. These executor implementations form the bulk of the code that is actually written by server application developers — nearly everything else is generated automatically by either the CIDL compiler and/or IDL 3.x compiler! Implementing the StockBroker component executor. We first present the implementation of the CCM StockBroker component, whose ports are shown here:

component StockBroker {	
   consumes StockName notifier_in;
   uses StockQuoter quoter_info_in;
};
	

StockBroker contains two ports that correspond to the following two roles it plays:

The following C++ code fragments illustrate one way to implement the StockBroker component. We start by defining a namespace for our executor.

namespace StockBroker_Impl {

We then define our StockBroker_Exec_i executor implementation, which inherits from the StockBroker_Exec local executor class generated by the IDL compiler from the executor IDL produced by CIAO's CIDL compiler when it processed the StockBroker_Impl composition shown in Step 1:

class StockBroker_Exec_i  : public virtual StockBroker_Exec {

The primary method in StockBroker_Exec_i we need to implement is push_notifier_in(), which is dispatched by the StockBroker's ORB when a StockDistributor publishes a StockName in response to a change in the price of a stock it's monitoring.

		void push_notifier_in (Stock::StockName *stock) {
		

If the name of the stock is "ACME ORB Inc." this method uses the session context managed by the component's container to obtain the object reference to the StockQuoter instance provided by the StockDistributor component, as follows:


		    if (stock->name == "ACME ORB Inc.") {
	  	        Stock::StockQuoter_var quoter = 
                  context_->get_connection_quoter_info_in ();
                  

After obtaining the object reference, the push_notifier_in() method calls back to the StockDistributor component to obtain the latest information about the stock it's interested in.

      		        Stock::StockInfo_var info = 
                      quoter->get_stock_info (stockname);
	                // ...Use this info for various purposes...
		          }
                }
                

The executor implementation also needs to implement the following container callback methods:

                              void  ccm_activate (void);
                              void ccm_passivate (void);
                              void  ccm_remove (void);
                              

The CCM container framework uses ccm_activate() to callback on the StockBroker component when the component is ready to receive requests. This method can initialize application-specific data members. The container framework also calls ccm_passivate() and ccm_remove() to enable the component to passivate and remove itself from service, respectively. We don't show the implementations of these methods since they aren't germane to the column. The container callback method that is of interest in this column is the following:

      void set_session_context (Components::SessionContext_ptr ctx) {
        context_ = CCM_StockBroker_Context::_narrow (ctx);
 
}

This method is called back when the component has been created, but before activation during the deployment phase, to store the context information within the executor implementation which is subsequently used during the push_notifier_in () operation shown above.

       };
  };
  

Some things to note about the implementation of the StockBroker component executor include:

Implementing the StockDistributor component executor. We now present the implementation of the StockDistributor component, whose ports are shown here:

component StockDistributor supports Trigger {
      publishes StockName notifier_out;
      provides StockQuoter quoter_info_out;
      attribute long notification_rate;
};

This CCM component extends the Trigger interface defined in [Schmidt:04b], which enables system administrator applications to start() and stop() instances of StockDistributor. It also publishes a StockName eventtype called notifier_out that is pushed to the StockBroker subscriber components when a stock value changes. In addition, it defines a StockQuoter facet called quoter_info_out, which defines a factory operation that returns object references that StockBroker components can use to obtain more information about a particular stock. Finally, this component defines the notification_rate attribute, which system administrator applications can use to control the rate at which the StockDistributor component checks the stock quote database and pushes changes to StockBroker subscribers.

The C++ implementation of the executor for the StockDistributor component is shown here:

namespace StockDistributor_Impl  {
 	    class StockDistributor_Exec_i : public virtual StockDistributor_Exec,
                                      public virtual ACE_Task_Base {
            

The StockDistributor_Exec_i class inherits from the StockDistributor_Exec local executor class generated by the IDL compiler from the executor IDL produced by CIAO CIDL compiler. It also inherits from ACE_Task_Base [C++NPv2], which is a base class provided by ACE that can be used to convert StockDistributor_Exec_i into an active object [POSA2]. When a system administrator application calls the start() operation, we use the ACE_Task_Base::activate() method to convert the executor into an active object, as follows:

		void start () {
		      activate ();
		}
		

The activate() method is defined in ACE_Task_Base to spawn a new thread of control, which automatically invokes the following svc() hook method, of which we show just a portion:

		virtual int svc () {
		     while (thr_mgr ()->testcancel () == 0) {
                 StockName_var stock;
                 // ... monitor the stock database for changes ...
                 stock.name = "ACME ORB Inc.";
                 context_->push_notifier_out (stock.in ());
		      }
		}
		

This method runs continuously, monitoring the database and pushing StockName events to StockBroker consumers when stocks change their values. It stops when it's canceled, which occurs when a system administrator application calls the stop() operation:

		void stop () {
		     thr_mgr ()->cancel ();
		}
		

The implementation of the ACE_Thread_Manager::cancel() method [C++NPv1] shown above sets an internal flag in the thread manager indicating the thread has been placed into the cancelled state, but doesn't perform asynchronous cancellation (which is problematic in C++ since asynchronous cancellation doesn't guarantee destructors are executed as the runtime stack is unwound). Since the StockDistributor component provides StockQuoter as a facet, the get_quoter_info_out() method can be implemented in our executor to return a new instance of StockQuoter_i:

  		CCM_StockQuoter_ptr get_quoter_info_out () {
		       return new StockQuoter_i; 
		}
		

To do that, however, we'll need to extend the executor skeleton of the StockQuoter facet and provide the following implementation:

namespace StockDistributor_Impl  {
          StockQuoter_i : public virtual CCM_StockQuoter {
             // operations and implementations
          };
};

The container for the StockDistributor component caches the local object of StockQuoter_i and maps the actual servant's reference it generates for the facet to the local object implementation that the component application provides. All calls on the generated servant are then delegated to the C++ class instance that implements the operation. For example, the StockQuoter_Servant generated by CIDL for the facet in the StockDistributor component is mapped to the implementation of the CCM_StockQuoter interface, which in our case happens to be StockQuoter_i. Any calls on operations defined within the StockQuoter interface are delegated to the implementation in the CCM_StockQuoter interface.

Note that the executor actually implements get_*() instead of provide_*(), which was shown as the IDL 2.x mapping of the provides keyword in our previous column [Schmidt:04b]. The IDL 2.x mapping presented in our previous column is an external client's view of the component. A client that is not component-enabled can get the facet by calling the provide_*() operation on the components reference. In contrast, get_*() is the server-side executor mapping of the facet. The component's provide_*() operation to retrieve the facet is mapped automatically to the executor's get_*() method, which is then implemented by component developers.

The final methods we need to implement in our executor are simply the accessor and mutator methods for the notification_rate attribute:

CORBA::Long notification_rate () { return rate_; }
void notification_rate (CORBA::Long rate) { rate_ = rate; }

There's a corresponding data member in the private part of the executor class to store the rate value:

	private:
	    CORBA::Long rate_;
    };	 
};

We omitted the implementations of the container callback operations on the StockDistributor_Exec_i class since the implementations are similar to the implementations in the StockBroker_Exec_i class.

Implementing the component homes. Instances of the StockBroker and StockDistributor components must be created by the CCM runtime system using the following component homes:

	home StockBrokerHome manages StockBroker {};
    home StockDistributorHome manages StockDistributor {};
    

The C++ implementation of StockBrokerHome is shown here:

class StockBrokerHome_i : public virtual StockBrokerHome_Exec  {
      Components::EnterpriseComponent_ptr create () {
         StockBroker_i broker = new StockBroker_i;
         return broker->_this ();
      }

}; 

The implementation of StockDistributorHome is nearly identical, so we omit it.

Finally, we define the two entry points into the dynamic link library (DLL) in which our executor code is contained, as follows:

extern "C" {
components::HomeExecutorBase_ptr
createStockBrokerHome_Impl (void) 
{
      return new StockBroker_Impl::StockBrokerHome_Exec_i;
} 

components::HomeExecutorBase_ptr
createStockDistributorHome_Impl (void) 
{
       return new StockBroker_Impl::StockDistributorHome_Exec_i;
} 
}

The motivation for this entry point is to allow the standard CCM deployment and configuration tools to locate the home within the executor DLL. One of the descriptors associated with a component is called the implementation artifact descriptor, which is an XML instance document based on an OMG-specified schema that maps the component name to the DLL in which the implementation resides. The document also contains the entry point that can be invoked by the tools to instantiate a home upon opening the DLL. The name of the entry point can be specified in the XML document, but the signature of the C-style function is defined in the CCM specification.

Concluding Remarks

This column illustrated how to implement the components in our stock quoter example application using the CCM container framework and Component Implementation Framework (CIF). We explained details of the three canonical steps involved in implementing these components, specifically:

  1. Creating component implementation definitions and compositions using IDL 3.x and CIDL.
  2. Compiling the component compositions to generate most of the glue code required by your application.
  3. Implementing the component and home executors that define the business logic of your application.

We showed a lot of C++ and IDL code in this column, but as we explained in Step 2, the CCM CIF generates most of it for you. The automatic generation of a fairly large amount of support code is one of the strengths of this approach over the older CORBA 2.x approach, where developers normally wrote this code for themselves.

Our next column will illustrate how to apply the new OMG Deployment and Configuration specification [D&C] to assemble components (e.g., group related components together and characterize their metadata that describes the components present in the assembly) and deploy your components and run your application (e.g., move the component assemblies to the appropriate nodes in the distributed system and invoke operations on components to perform the application logic). As always, if you have any comments on this column or any previous one, please contact us at:[email protected].

References

[CORBA3] "CORBA Components," OMG Document formal/2002-06-65, June 2002.

[C++NPv1] Schmidt, D. and S. Huston. C++ Network Programming: Mastering Complexity with ACE and Patterns, Addison-Wesley, 2002. http://www.cs.wustl.edu/~schmidt/ACE/book1/

[C++NPv2] Schmidt, D. and S. Huston. C++ Network Programming: Systematic Reuse with ACE and Frameworks, Addison-Wesley, 2003. http://www.cs.wustl.edu/~schmidt/ACE/book2/

[D&C] "Deployment and Configuration of Component-based Distributed Applications Specification," OMG Document ptc/2003-07-08, July 2003.

[GoF], Gamma, E., R. Helms, R. Johnson, and J. Vlissides. Design Patterns, Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995.

[POSA1] Buschmann, F., R. Meunier, H. Rohnert, P. Sommerlad, M. Stal. Pattern Oriented Software Architecture: A System of Patterns, Wiley & Sons, 1996.

[POSA2] Schmidt, D., H. Rohnert, M. Stal, and F. Buschmann. Pattern Oriented Software Architecture: Concurrent and Networked Objects, Wiley & Sons, 2000. http://www.cs.wustl.edu/~schmidt/POSA/

[Schmidt04a] Schmidt, D. and S. Vinoski. "The CORBA Component Model, Part 1: Evolving Towards Component Middleware," C/C++ Users Journal, February 2004. http://www.cuj.com/documents/s=9039/cujexp0402vinoski/

[Schmidt04b] Schmidt, D. and S. Vinoski. "The CORBA Component Model, Part 2: Defining Components with the IDL 3.x Types," C/C++ Users Journal, April 2004. http://www.cuj.com/documents/s=9152/cujexp0404vinoski/

[Schmidt04c] Schmidt, D. and S. Vinoski. "The CORBA Component Model, Part 3: The CCM Container Architecture and Component Implementation Framework," C/C++ Users Journal, April 2004. http://www.cuj.com/documents/s=9301/cujexp0409vinoski/

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.