Fractal | Fractal ADL | Cecilia
 

Overview

This page shows how to use cloneable components in a Cecilia program.

Cloneable components allow to dynamically create/destroy component instances by cloning pre-existing components. They are useful for encapsulating dynamically-created elements of an application like threads in an operating system.

A component is said cloneable if its controller descriptor is cloneable. In such case, the Cecilia toolchain will automatically add a factory controller interface and generate the appropriate implementation of this interface.

In order to use cloneable components, one must:

  • create a factory of such components;
  • use a memory allocator;
  • and most of all, bind the pieces together appropriately.

Download

To get the Cloneable Components example based on Makefile, download one of the following packages:

To get the Cloneable Components example based on Maven, download one of the following packages:

For instructions on compiling and running the example, see the README.txt file.

Cloneable Components application architecture

The architecture of the application is depicted in the following figure:

Architecture of the Cecilia Cloneable example

The main component uses the factory to create instances of MyComponent, calls several methods on the created instances, and destroys them. A memory allocator is needed.

Here is the architecture description of the application, written in the Cecilia ADL Language.

File App.fractal:

<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
  "classpath://org/objectweb/fractal/cecilia/adl/parser/xml/cecilia.dtd">

<!--=========================================================================-->
<!-- Component that defines the entire application                           -->
<!--=========================================================================-->
<definition name="App">

    <interface name="main" role="server" signature="boot.api.Main" />

    <!-- Component that defines the main() method -->
    <component name="main" definition="Main"/>
    <!-- Memory allocator -->
    <component name="allocator" definition="unix.memory.Malloc"/>
    <!-- TODO add own block allocator on a fixed size chunk of memory ? -->

    <!-- Factory for creating components -->
    <component name="factory" definition="Factory"/>
    <!-- Cloneable component type. Must be included and bound to the factory -->
    <component name="mycomponent" definition="MyComponent"/>


    <!-- The main component uses the factory -->
    <binding client="main.factory" server="factory.mycomponentFactory" />
    <!-- Binding between the factory and the cloneable component type -->
    <binding client="factory.mycomponent-factory" server="mycomponent.factory" />
    <!-- Bindings with the memory allocator, both the factory and the cloneable
         component need them. "factory-allocator" is a standard interface of
         cloneable components. -->
    <binding client="factory.allocator" server="allocator.allocator" />
    <binding client="mycomponent.factory-allocator" server="allocator.allocator"/>

    <binding client="this.main" server="main.main" />

</definition>

Cloneable components type.

The cloneable component is of MyComponent type. A MyComponent contains 3 MyComponent2, each of which contains a MyComponent3, which contains some code.

File MyComponent.fractal:

<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
  "classpath://org/objectweb/fractal/cecilia/adl/parser/xml/cecilia.dtd">

<!--=========================================================================-->
<!-- Component that will be cloned, top-level definition                     -->
<!--=========================================================================-->
<definition name="MyComponent">

    <!-- Three sub-components with the same interface -->
    <interface name="i1" role="server" signature="Itf" />
    <interface name="i2" role="server" signature="Itf" />
    <interface name="i3" role="server" signature="Itf" />

    <component name="o1" definition="MyComponent2" />
    <component name="o2" definition="MyComponent2" />
    <component name="o3" definition="MyComponent2" />

    <binding client="this.i1" server="o1.i" />
    <binding client="this.i2" server="o2.i" />
    <binding client="this.i3" server="o3.i" />
    
    <!-- Bindings to the memory allocator. "factory-allocator" is a standard
         interface of cloneable components.-->
    <!-- since 2.0.1 these bindings are no more necessary and are added
         automatically -->
    <!-- 
    <binding client="o1.factory-allocator" server="this.factory-allocator"/>
    <binding client="o2.factory-allocator" server="this.factory-allocator"/>
    <binding client="o3.factory-allocator" server="this.factory-allocator"/>
    -->

    <!-- Tell the toolchain that this component is cloneable -->
    <controller desc="cloneable"/>
</definition>

File MyComponent2.fractal:

<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
  "classpath://org/objectweb/fractal/cecilia/adl/parser/xml/cecilia.dtd">

<!--=========================================================================-->
<!-- Component that will be cloned as part of the cloning of a MyComponent   -->
<!-- component                                                               -->
<!--=========================================================================-->
<definition name="MyComponent2">

    <!-- A single sub-component -->
    <interface name="i" role="server" signature="Itf" />

    <component name="o" definition="MyComponent3" />

    <binding client="this.i" server="o.i" />

    <!-- Binding to the memory allocator. "factory-allocator" is a standard
         interface of cloneable components.-->
    <!-- since 2.0.1 these bindings are no more necessary and are added
         automatically -->
    <!-- 
    <binding client="o.factory-allocator" server="this.factory-allocator"/>
    -->

    <!-- Tell the toolchain that this component is cloneable -->
    <!-- since 2.0.1 this line is no more necessary since this definition is 
         used in a cloneable composite. -->
    <!-- 
    <controller desc="cloneable"/>
    -->
</definition>

File MyComponent3.fractal:

<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
  "classpath://org/objectweb/fractal/cecilia/adl/parser/xml/cecilia.dtd">

<!--=========================================================================-->
<!-- Component that will be cloned as part of the cloning of a MyComponent2  -->
<!-- component                                                               -->
<!--=========================================================================-->
<definition name="MyComponent3">

    <interface name="i" role="server" signature="Itf" />

    <!-- This component contains the actual code -->
    <content class="mycomponent3" language="thinkMC" />

    <!-- Tell the toolchain that this component is cloneable -->
    <!-- since 2.0.1 this line is no more necessary since this definition is 
         used in a cloneable composite. -->
    <!-- 
    <controller desc="cloneable"/>
    -->
</definition>

All those components export at least an Itf interface:

/**
 * Methods of a MyComponent component.
 * TODO: use an attribute to get rid of the getter&setter ?
 */
interface Itf {

  void setMyComponentNumber(int val);

  int getMyComponentNumber();

  /**
   * Dummy workload.
   */
  void eatCPU();

}

Cloneable components use the memory allocator (it's not just the factory which uses it), in order to allocate the internal data structures for themselves and their subcomponents if any.

Notice:

  • the <controller desc="cloneable"/> to tell the toolchain that a component is cloneable;
  • the name of the memory allocator client interface for cloneable component must be factory-allocator.

Since 2.0.1:

  • It is not necessary to add the <controller desc="cloneable"/> tag on every sub components of a cloneable composite. This tag will be added automatically added.
  • Similarly, bindings from factory-allocator client interfaces of the sub-components of a cloneable composite are bound automatically.

There's nothing special with the code that implements the Itf interface:

/* all the includes *except* cecilia.h */
#include <stdio.h>

/** Component internal data. */
DECLARE_DATA {
    jint componentNumber;
} ;

/* Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

// -----------------------------------------------------------------------------
// Implementation of the 'Itf' interface.
// -----------------------------------------------------------------------------

void METHOD(i, setMyComponentNumber) (void *_this, jint val) {
    DATA.componentNumber = val;
}

jint METHOD(i, getMyComponentNumber) (void *_this) {
    return DATA.componentNumber;
}

void METHOD(i, eatCPU) (void * _this) {
    volatile int i = 0;
    while (i < (1000 + DATA.componentNumber*10)) {
        i++;
    }
}

Factory of cloneable components

The factory uses:

  • the cloneable component type, so as to know what type is going to be cloned;
  • the memory allocator, in order to allocate the internal data structures for an instance of the top-level MyComponent component type.

File Factory.fractal:

<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
  "classpath://org/objectweb/fractal/cecilia/adl/parser/xml/cecilia.dtd">

<!--=========================================================================-->
<!-- Component that provides a factory for MyComponents                      -->
<!--=========================================================================-->
<definition name="Factory">
  
  <!-- Operations for creating/destroying dynamic components -->
  <!-- MyComponents -->
  <interface name="mycomponentFactory" role="server" signature="MyComponentFactory"/>


  <!-- Needed services -->
  <interface name="allocator" role="client" signature="memory.api.Allocator"/>


  <!-- Links to the cloneable components used for instanciation -->
  <!-- MyComponents -->  
  <interface name="mycomponent-factory" role="client" signature="fractal.api.Factory" />

  <content class="factory" language="thinkMC" />

  <!-- Attributes of the component for easy initialization of the component count
       (could also add a configuration interface) -->
  <attributes signature="FactoryAttributes">
    <attribute name="count" value="0" />
  </attributes>

</definition>

It provides interface MyComponentFactory, defined in file MyComponentFactory.idl:

import fractal.api.Component;

/**
 * This interface provides creation and destruction of MyComponent components
 */
interface MyComponentFactory {

  int CREATE_COMPONENT_OK = 0;
  int CREATE_COMPONENT_ERROR_MEM = 1;

  int createMyComponentInstance(out Component mycomponentCI);

  void destroyMyComponentInstance(Component mycomponentCI);

}

This factory creates the components (and all their subcomponents), using the standard newFcInstance method. It also retrieves all the interfaces provided by the newly created component (i1, i2 and i3), and on each interface, calls the method which sets a number of MyComponent3 instance. This number is fetched by the Main component, described below.

/* all the includes *except* cecilia.h */
#include <stdio.h>

/** Component internal data, empty here. */
DECLARE_DATA {
} ;

/* Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>
/* Include used interface definitions */
#include <Itf.idl.h>

// -----------------------------------------------------------------------------
// Implementation of the 'factory' interface.
// -----------------------------------------------------------------------------

int METHOD(mycomponentFactory, createMyComponentInstance) (void * _this, fractal_api_Component * mycomponentCI) {
    fractal_api_Component mycomponent;
    Itf itf;
    jint result;

    result = CALL(REQUIRED.mycomponent_factory, newFcInstance, &mycomponent);

    /* Instanciation can fail due to memory exhaustion */
    if (result != fractal_api_ErrorConst_OK) {
        return MyComponentFactory_CREATE_COMPONENT_ERROR_MEM;
    }

    result = CALL(mycomponent, getFcInterface, "i1", (void *)&itf);
    if (result == fractal_api_ErrorConst_OK) {
        CALL(itf, setMyComponentNumber, ++ATTRIBUTES.count);
    }
    else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
        fprintf(stderr, "ERR: no interface 'i1' on component !\n");
    }
    else {
        fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
    }

    
    result = CALL(mycomponent, getFcInterface, "i2", (void *)&itf);
    if (result == fractal_api_ErrorConst_OK) {
        CALL(itf, setMyComponentNumber, ++ATTRIBUTES.count);
    }
    else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
        fprintf(stderr, "ERR: no interface 'i2' on component !\n");
    }
    else {
        fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
    }

    
    result = CALL(mycomponent, getFcInterface, "i3", (void *)&itf);
    if (result == fractal_api_ErrorConst_OK) {
        CALL(itf, setMyComponentNumber, ++ATTRIBUTES.count);
    }
    else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
        fprintf(stderr, "ERR: no interface 'i3' on component !\n");
    }
    else {
        fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
    }
    
    *mycomponentCI = mycomponent;
    return MyComponentFactory_CREATE_COMPONENT_OK;
}

void METHOD(mycomponentFactory, destroyMyComponentInstance) (void * _this, fractal_api_Component mycomponentCI) {
    CALL(REQUIRED.mycomponent_factory, destroyFcInstance, mycomponentCI);
}

Destroying a component (and all its subcomponents) is achieved through the standard destroyFcInstance method.

Main component

This component contains the main() method. This method does the following:

  • repeat until some number of iterations is reached:
    • create an instance of MyComponent;
    • for each interface provided by the instance of MyComponent, get the number of MyComponent3 instance, and print it;
    • waste some CPU;
    • destroy the instance.

File main.c:

/* all the includes *except* cecilia.h */
#include <stdio.h>

/** Component internal data, empty here. */
DECLARE_DATA {
} ;

/* Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>
/* Include used interface definitions */
#include <Itf.idl.h>

// -----------------------------------------------------------------------------
// Implementation of the 'main' boot interface.
// -----------------------------------------------------------------------------

// int main(int argc, string[] argv)
int METHOD(r, main) (void *_this, int argc, char *argv[]){
    juint i;
    jint val;
    jint result;
    fractal_api_Component mycomponent;
    Itf itf;

    printf("Entering main\n");
    for (i = 0; i < ATTRIBUTES.numberOfComponents; i++) {
        result = CALL(REQUIRED.factory, createMyComponentInstance, (void *)&mycomponent);
        if (result != MyComponentFactory_CREATE_COMPONENT_OK) {
            fprintf(stderr,"Couldn't create component !\n");
        }
        else {
            // Retrieve and call 1st interface of MyComponent instance.
            result = CALL(mycomponent, getFcInterface, "i1", (void *)&itf);
            if (result == fractal_api_ErrorConst_OK) {
                val = CALL(itf, getMyComponentNumber);

                printf("\t%d\t",val);

                // Call the workload.
                CALL(itf, eatCPU);
            }
            else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
                fprintf(stderr, "ERR: no interface 'i1' on MyComponent instance !\n");
            }
            else {
                fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
            }

            // Retrieve and call 2nd interface of MyComponent instance.
            result = CALL(mycomponent, getFcInterface, "i2", (void *)&itf);
            if (result == fractal_api_ErrorConst_OK) {
                val = CALL(itf, getMyComponentNumber);

                printf("%d\t",val);

                // Call the workload.
                CALL(itf, eatCPU);
            }
            else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
                fprintf(stderr, "ERR: no interface 'i2' on MyComponent instance !\n");
            }
            else {
                fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
            }

            // Retrieve and call 3rd interface of MyComponent instance.
            result = CALL(mycomponent, getFcInterface, "i3", (void *)&itf);
            if (result == fractal_api_ErrorConst_OK) {
                val = CALL(itf, getMyComponentNumber);

                printf("%d\t",val);

                // Call the workload.
                CALL(itf, eatCPU);
            }
            else if (result == fractal_api_ErrorConst_NO_SUCH_INTERFACE) {
                fprintf(stderr, "ERR: no interface 'i3' on MyComponent instance !\n");
            }
            else {
                fprintf(stderr, "ERR: getFcInterface returned a strange return value !\n");
            }

            // Flush stdout by printing a newline.
            printf("\n");

            // Don't forget to destroy the component :-)
            CALL(REQUIRED.factory, destroyMyComponentInstance, mycomponent);
        }
    }
    printf("Exiting main\n");

    return 0;
}

Notice the #include of the Itf.idl.h auto-generated file, at the beginning. This line is needed to use the Itf IDL type in C. If the Itf type was used in the prototype of a method that this C file implements, you wouldn't need this line: the Cecilia-generated files corresponding to that component already contain it.

 
2001-2009 © ObjectWeb Consortium  | Last Published: 2009-04-21 14:35  | Version: 2.1.0