Fractal | Fractal ADL | Cecilia
 

Overview

This page shows how to code a simple Hello-World example under Cecilia.

This example is described in a top-down fashion:

  1. it starts with the overall application architecture, written in the XML based Cecilia ADL language
  2. then it shows the definition of the interfaces of the components involved, written in the Cecilia IDL language
  3. and afterwards the implementation of the 2 primitive components client and server, in the thinkMC language

Informations concerning source files organization, compilation and execution instructions can be found on the Compiling and Running page.

Defining the HelloWorld application architecture

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

Architecture of the Hello-World example

Following this architecture, we can write the whole architecture description in an XML file, in the Cecilia ADL Language.

File helloworld/Helloworld.fractal:

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

<!--=========================================================================-->
<!-- Helloworld example                                                      -->
<!--                                                                         -->
<!--   This ADL defines a composite component 'Helloworld' containing two    -->
<!--   sub components 'client' and 'server'. The 'client' provides an        -->
<!--   interface 'r' and is bound to an interface 'r' provided by the        -->
<!--   'server'.                                                             -->
<!--=========================================================================-->
<definition name="helloworld.Helloworld">

  <!--=======================================================================-->
  <!-- 'main' interface. The entry point of the Helloworld application       -->
  <!--=======================================================================-->
  <interface name="main" role="server" signature="boot.api.Main" />

  <!--=======================================================================-->
  <!-- The client sub-component                                              -->
  <!--=======================================================================-->
  <component name="client">

    <!-- Interfaces of the client component -->
    <interface name="r" role="server" signature="boot.api.Main" />
    <interface name="s" role="client" signature="helloworld.Service" />

    <!-- Implementation of the client component -->
    <content class="helloworld.client" />
  </component>

  <!--=======================================================================-->
  <!-- The server sub-component                                              -->
  <!--=======================================================================-->
  <component name="server">

    <!-- Interface of the server component -->
    <interface name="s" role="server" signature="helloworld.Service" />

    <!-- Implementation of the server component -->
    <content class="helloworld.server" />

    <!-- Attributes of the server component -->
    <attributes signature="helloworld.ServiceAttributes">
      <attribute name="header" value="-> " />
      <attribute name="count" value="1" />
    </attributes>
  </component>

  <!--=======================================================================-->
  <!-- Bindings                                                              -->
  <!--=======================================================================-->

  <!-- 
    the interface 'main' exported by this composite is bound to the interface
    'r' of the 'client' component. 
  -->
  <binding client="this.main" server="client.r" />

  <!-- 
    the client interface 's' of the 'client' component is bound to the server 
    interface 's' of the 'server' component.
  -->
  <binding client="client.s" server="server.s" />
</definition>

Things to note:

  • the entry point of the application is the interface main of type boot.api.Main. Every Cecilia executable application should provide it.

    This interface is defined in the Cecilia base library. You can just pass on for the moment, and just see how it is later implemented in the client primitive component.

  • the client component provides the aforementioned boot.api.Main interface and requires a helloworld.Service interface.

    This interface is provided by the server component, which also has a helloworld.ServiceAttributes, defining the attributes. These interfaces will be defined in the next section.

  • the implementation of the client and server primitive components is specified by the content XML node. It specifies the name of the .c file containing the concrete implementation (the value of the class XML attribute).

    In this example the primitive components are written in the thinkMC language. For the moment you can just look at the client and server implementations to get a glimpse of the thinkMC language syntax and features. Afterwards, you might want to look at the thinkMC language page.

Defining the component interfaces

In Cecilia, in order to define component interfaces, it is necessary to use a language other than C, which does not support natively the interface construct. This language is Cecilia IDL, a pseudo-Java interface language.

Cecilia IDL supports two types of constructs: interfaces (for component methods) and records (for component attributes). Here we will see an example of each of them.

The IDL definition below specifies an interface, called Service, which is located in the package helloworld, and which provides a method to print a string.

File helloworld/Service.idl:

package helloworld;

interface Service {
  void print(const string msg);
}

The IDL definition below defines a record called ServiceAttributes, which is in the package helloworld, which has an int and a string field.

File helloworld/ServiceAttributes.idl:

package helloworld;

record ServiceAttributes {
  int count;
  string header;
}

Implementing the primitive components

Once the interfaces have been defined, it is possible to implement them in primitive components, specifically the client and server which are in the HelloWorld description.

In this context, the two components client and server are written in the thinkMC primitive programming language, which provides C syntactic-sugar facilities to program as if you were using an higher level language, such as instance fields and method calls.

Server component

File helloworld/server.c:

#include <stdio.h>

/** Declare component internal data */
DECLARE_DATA {
  // no data
};

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

// -----------------------------------------------------------------------------
// Implementation of the service interface.
// -----------------------------------------------------------------------------

// void print(string msg)
void METHOD(s, print)(void *_this, const char *msg) 
{
  int i;
  char* header = ATTRIBUTES.header;
  if (header == NULL) {
    header = (char *)"";
  }
  
  printf("Server: begin printing...\n");
  for (i = 0; i < ATTRIBUTES.count; ++i) {
    printf("%s%s\n", header, msg);
  }
  
  printf("Server: print done\n");
}

The server component implementation starts with a standard include (stdio.h), then with a DECLARE_DATA to hold this primitive component instance variables (in this basic example, there are no instance variables), then there is the cecilia.h include, and after the implementation of the interfaces this component offers, specifically the Service interface.

Things to note:

  • the DECLARE_DATA section is mandatory, and may optionally contain instance variables for a component instance, which may be accessed as DATA.<fieldName>. In this case it is empty.
  • the print method declaration makes use of a METHOD macro, passing the name of the server interface implemented (i.e. s see again the XML architecture description) and the name of the method of that interface which is implemented by the primitive component (in this case, print). The first METHOD parameter must always be void *_this, while the other ones depend on the argument types of the interface method being implemented. In the case of the Service print method, the type is a string, which is mapped to char * in the C programming language. See more on the IDL to C page for further references on how IDL types are mapped to C types.

Client component

File helloworld/client.c:

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

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

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

// int main(int argc, string[] argv)
int METHOD(r, main) (void *_this, int argc, char *argv[]){

  // call the 'print' method of the 's' client interface.
  CALL(REQUIRED.s, print, "hello world");
  
  // call again the same method to look at invocation count
  CALL(REQUIRED.s, print, "hello world");

  
  return 0;
}

Things to note:

  • the client component has no instance variables, so the mandatory DECLARE_DATA section is empty
  • the client component, as described in the XML architecture description, provides a server interface named r of type boot.api.Main. This interface has one method called main, which is the one implemented by the int METHOD(r, main)(void *_this, int argc, char *argv[]) definition. Again, the string[] argv parameter type in the boot.api.Main interface definition is mapped to char *argv[] n the C counterpart. See more on the IDL to C page for further references on how IDL types are mapped to C types.
  • the method invocations that the client component performs calling methods on its client interfaces are made using the CALL macro, passing (REQUIRED.<clientInterfaceName>, <interfaceMethod>, arg_1, ..., arg_n ) So specifically it calls on its s client interface the print method, passing "helloworld" as the first and only argument.

Adding an invocation counter in the server component

In the previous section, both client and server components did not contain instance data. In this section, we will see how to define, initialize and use an instance field. To do so, we will introduce a nrOfInvocations field in the server component that is initialized to 0 and then incremented each time the print method is called.

ServerWithCounter component implementation

File helloworld/serverWithCounter.c:

#include <stdio.h>

/** Declare component internal data */
DECLARE_DATA {
  int nrOfInvocations;
};

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

// -----------------------------------------------------------------------------
// Implementation of the constructor.
// -----------------------------------------------------------------------------

void CONSTRUCTOR(void *_this) {
  DATA.nrOfInvocations = 0;
}

// -----------------------------------------------------------------------------
// Implementation of the service interface.
// -----------------------------------------------------------------------------

// void print(string msg)
void METHOD(s, print)(void *_this, const char *msg)
{
  int i;
  char* header = ATTRIBUTES.header;
  if (header == NULL) {
    header = (char *)"";
  }

  printf("Server: begin printing...\n");
  for (i = 0; i < ATTRIBUTES.count; ++i) {
    printf("%s%s\n", header, msg);
  }

  // increment the number of invocations received by this component instance
  DATA.nrOfInvocations++;

  printf("Server: print done (Number of invocations up to now: #%d)\n", DATA.nrOfInvocations);
}

Things to note:

  • The DECLARE_DATA section contains the definition of the nrOfInvocations field of type int.
  • In order to initialize its instance data, the server component must implement a constructor. This function accesses and initializes the nrOfInvocations instance field using the DATA.nrOfInvocations construct.

HelloworldWithCounter component definition

We can now write a new ADL that defines a composite component containing the original client component and the new server component. To do so, rather than rewriting a new ADL from scratch, we will extends the original Helloworld ADL. For more info on ADL inheritance see the Fractal ADL tutorial.

File helloworld/HelloworldWithCounter.fractal:

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

<!--=========================================================================-->
<!-- HelloworldWithCounter example                                           -->
<!--                                                                         -->
<!--   This ADL extends the 'Helloworld' ADL adds a 'lifecycle-controller'   -->
<!--   interface to the 'server' sub component and changes its               -->
<!--   implementation.                                                       -->
<!--=========================================================================-->
<definition name="helloworld.HelloworldWithCounter" extends="helloworld.Helloworld">

  <!--=======================================================================-->
  <!-- Override the server sub-component                                     -->
  <!--=======================================================================-->
  <component name="server">

    <!-- Override the implementation of the server component -->
    <content class="helloworld.serverWithCounter" hasConstructor="true" />
  </component>
</definition>

Things to note:

  • In the server component it is neither necessary to redefine the s interface, nor the attributes element, since they are inherited from the helloworld.Helloworld ADL. The only thing to do is to override the specification of the implementation of the component (i.e. the content element).
  • Since the implementation of the server component provides a constructor, the content element must contains the attribute hasConstructor="true".

Running the example

If you want to download and play with the sources of the examples, see the Compile and run the Hello-World example page.

 
2007-2009 © ObjectWeb Consortium  | Last Published: 2009-04-21 14:33  | Version: 2.1.0