Fractal ADL Documentation

See:
          Description

Packages
org.objectweb.fractal.adl Defines the Fractal ADL framework.
org.objectweb.fractal.adl.arguments A Fractal ADL module to define parameterizable components.
org.objectweb.fractal.adl.attributes A Fractal ADL module to define component attributes.
org.objectweb.fractal.adl.bindings A Fractal ADL module to define component bindings.
org.objectweb.fractal.adl.comments A Fractal ADL module to add comments in component definitions.
org.objectweb.fractal.adl.components A Fractal ADL module to define sub components.
org.objectweb.fractal.adl.coordinates A Fractal ADL module to define component coordinates.
org.objectweb.fractal.adl.implementations A Fractal ADL module to define component implementations.
org.objectweb.fractal.adl.interfaces A Fractal ADL module to define component interfaces.
org.objectweb.fractal.adl.loggers A Fractal ADL module to define component logger names.
org.objectweb.fractal.adl.nodes A Fractal ADL module for distributed component deployment.
org.objectweb.fractal.adl.types A Fractal ADL module to define interface types.
org.objectweb.fractal.adl.xml Provides XML based implementations of Parser and Loader.

 

Fractal ADL is an extensible Architecture Description Language for the Fractal component model.

Overview

Fractal ADL is not a fixed language, but a set of ADL modules from which various ADLs can be constructed. The idea is to do aspect oriented "programming" at the ADL level: each module is intended to correspond to an ADL "aspect", such as architecture, implementation, deployment, logging, versioning or packaging. See the Fractal ADL tutorial for more details about the Fractal ADL modules.

Since the Fractal ADL does not impose a concrete syntax, the common representation used for interoperability between Fractal ADL tools are abstract syntax trees (AST). An AST provides both a generic API, similar to DOM (but not DOM itself, in order to be independent of XML), and a typed API. The generic API is defined in the Node interface. The typed API is defined by the set of AST interfaces provided by all the modules of the ADL.

The Fractal ADL package defines various Fractal components (described with Fractal ADL definitions) that can be composed to create various Fractal ADL tools, for a given set of Fractal ADL modules. The main tool is a component factory tool, that can create Fractal components from Fractal ADL definitions. This tool uses a loader tool, a compiler tool, and a builder (also called backend) tool (see Figure 1):


Figure 1: overall architecture of the Fractal ADL factory component
The loader tool is made of a chain of primitive components (see Figure 2). The bottom right component creates ASTs from concrete ADL definitions. By default this component looks for definitions in the classpath, and expects definitions to be written with an XML syntax. The other components verify and optionally transform the ASTs. Each component corresponds to a Fractal ADL module, and performs computations related to this module. For example, a BindingLoader component verifies all the bindings that are declared in an AST, an AttributeLoader verifies all the attributes declared in an AST, and so on. A loader component with sub components for the A, B and C modules can load definitions using any set of modules, such as a definition using the B, C and D modules: however, in this case, no verification will be made for the D module, and the component for the A module will be useless.


Figure 2: architecture of the loader component

The compiler tool is made of a list of compiler components (see Figure 3). Each compiler component produces tasks related to a module or to a set of modules. For example, the BindingCompiler component produces tasks to create bindings, and the AttributeCompiler component produces tasks to initialize attributes. The tasks produced by a compiler component A may depend on the tasks produced by a compiler component B. In this case, the component compiler B must be executed before A. For example, the compiler component that produces tasks to create components must be run before the BindingCompiler and AttributeCompiler components, since the tasks produced by these compilers depend on component creation tasks.  Each compiler component depends on a corresponding builder component that defines the precise behavior of the tasks produced by this component.


Figure 3: architecture of the compiler component

The builder tool is made of a set of independent builder components (see Figure 4). Each builder component defines a precise behavior for the tasks produced by a specific compiler component.


Figure 4: architecture of the builder component

Defining a new Fractal ADL module

Let's suppose we have defined the following LoggerController interface to control the logging of components:

public interface LoggerController {
  String getLoggerName ();
  void setLoggerName (String logger);
  int getLogLevel ();
  int setLogLevel (int level);
}

We now want to define a new Fractal ADL module to define the initial logger name and level of components, which, in XML, will have the following syntax:

<definition name="...">
  ...
  <logger name="logger" level="DEBUG"/>
</definition>

AST interfaces

The first step is to define the AST interfaces for this new module. A first interface defines a "logger" node, with two attributes named "name" and "level". This is done by declaring a getter and a setter method for each attribute (all attributes must be of type String):

public interface Logger {
  String getName ();
  void setName (String name);
  String getLevel ();
  void setLevel (String level);
}

In order to be able to add this node as a child of other nodes, another interface is needed:

public interface LoggerContainer {
  Logger getLogger ();
  void setLogger (Logger logger);
}

This interface will be implemented by the AST nodes that will be able to contain Logger nodes. Here again there is one getter and setter method per sub node, with the sub node interface as argument(*).

XML DTD

We can now define a DTD for ADL definitions using logger definitions. This can be done by starting from an existing DTD. The first step is to declare the new AST nodes:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!-- ********************************************************************** -->
<!--                          AST nodes definitions                         -->
<!-- ********************************************************************** -->

<?add ast="definition" itf="org.objectweb.fractal.adl.Definition" ?>

...

<!-- logger module -->
<?add ast="logger"     itf="org.objectweb.fractal.adl.logger.Logger" ?>
<?add ast="definition" itf="org.objectweb.fractal.adl.logger.LoggerContainer" ?>
<?add ast="component"  itf="org.objectweb.fractal.adl.logger.LoggerContainer" ?>

The first line in the logger module section declares that the (new) "logger" AST node must implement the "Logger" interface. The last two lines declare that the (existing) "definition" and "component" AST nodes must also implement the "LoggerContainer" interface. The effect is that these nodes will be able to contain at most one "logger" sub node (Note: the AST interfaces names can be arbitrary, but the AST node names must correspond to the names used in the getter and setter methods of the AST interfaces. For example a "getXyz" method in an AST interface means that a "xyz" AST node must be declared).

We can then define a new "logger" DTD element, and modify the existing "definition" and "component" DTD elements:

<!-- ********************************************************************** -->
<!--                          XML syntax definition                         -->
<!-- ********************************************************************** -->

<!ELEMENT definition (interface*,component*,binding*,content?,attributes?,controller?,template-controller?,logger?) >


<!ELEMENT component (interface*,component*,binding*,content?,attributes?,controller?,template-controller?,logger?) >

...

<!ELEMENT logger EMPTY >
<!ATTLIST logger
  name CDATA #REQUIRED
  level CDATA #REQUIRED
>

Note that we could have introduced the "logger" sub element anywhere in the "definition" and "component" sub element list. Indeed the precise regular expressions used in DTD element declarations are not important, provided the sub element arities are consistent with the AST sub node arities.

Loader component

The logger module does not need semantic verifications, so we do not really need a loader component for this module. We can however provide a basic loader component to check that the name and level of a logger node are not null (this property is verified by the parser only if we use an XML syntax, and if a validating parser is used; so it is not useless to check it here):

public class LoggerLoader extends AbstractLoader {
   
  public Definition load (String name, Map context)
    throws ADLException
  {
    Definition d = clientLoader.load(name, context);
    checkNode(d, extend);
    return d;
  }

  private void checkNode (Object node)
    throws ADLException
  {
    if (node instanceof LoggerContainer) {
      LoggerContainer container = (LoggerContainer)node;
      if (container.getLogger() != null) {
        Logger l = container.getLogger();
        if (l.getName() == null || l.getLevel() == null) {
          throw new ADLException("Name or level missing", (Node)l);
        }
      }
    }
    if (node instanceof ComponentContainer) {
      Component[] comps = ((ComponentContainer)node).getComponents();
      for (int i = 0; i < comps.length; i++) {
        checkNode(comps[i], extend);
      }
    }
  }
}

The AbstractLoader class defines an abstract loader component with a clientLoader client interface. The LoggerLoader loads a definition by using this clientLoader interface. It then recursively visits the returned AST and checks all the nodes that implement the LoggerContainer interface, and finally returns the AST to the caller.

Compiler component

The logger module needs a compiler component to create tasks that will set the initial name and level of loggers. The first step to implement this compiler component is to define the builder client interface it will use:

public interface LoggerBuilder {
  void setLogger (Object component, String name, String level) throws Exception;
}

The component argument is of type Object (instead of Component) because we cannot assume here that ADL definitions will be instantiated with the Fractal API: a builder component may choose to create components with the Java Reflection API instead, for example. Note also that we do not pass the Logger AST node directly to the setLogger method, in order to clearly separate the compiler and builder component roles: compiler components create tasks from ASTs, and buider components execute these tasks without needing full access to the AST.

public class LoggerCompiler implements PrimitiveCompiler, BindingController {
 
  private LoggerBuilder builder; // the builder client interface
 
  public String[] listFc () { ... }
  public Object lookupFc (String clientItfName) { ... }
  public void bindFc (String clientItfName, Object serverItf) { ... }
  public void unbindFc (String clientItfName) { ... }
   
  public void compile (List path, ComponentContainer container, TaskMap tasks, Map context)
    throws ADLException

  {
    if (container instanceof LoggerContainer) {
      Logger l = ((LoggerContainer)container).getLogger();
      if (l != null) {
        InstanceProviderTask c = (InstanceProviderTask)tasks.getTask("create", container);
        SetLoggerTask t = new SetLoggerTask(builder, l.getName(), l.getLevel());
        t.setInstanceProviderTask(c);
        tasks.add("logger", container, t);
      }
    }
  }
 
  static class SetLoggerTask extends AbstractConfigurationTask {

    private LoggerBuilder builder;
   
    private String name, level;
   
    public SetCoordinatesTask (...) { ... }
   
    public void execute (final Object context) throws Exception {
      Object component = getInstanceProviderTask().getResult();
      builder.setLogger(component, name, level);
    }

    public Object getResult() { return null; }
    public void setResult (Object result) { }
  }
}

The compiler component implementation is simple: the compile method, which is called for each ComponentContainer node in the AST by the main compiler component (see the Overview), creates a SetLoggerTask for each Logger node, and adds a dependency between this task and the task that creates the component for which the logger must be initialized (this task is found thanks to the tasks map, which contains the tasks already created by the previous compiler components). The SetLoggerTask task just calls the setLogger method on the client LoggerBuilder interface (the component whose logger must be initialized is the result of the InstanceProviderTask on which this task depends).

Builder components

Several implementations of the builder component for the logger module are possible. An implementation using the Fractal API is the following:

public class FractalLoggerBuilder implements LoggerBuilder {
 
  public void setLogger (Object component, String name, String level)
    throws Exception
  {   
    Component c = (Component)component;
    LoggerController logCont =
      (LoggerController)c.getFcInterface("logger-controller");
    logCont.setLoggerName(name);
    logCont.setLogLevel(level);
  }
}

an implementation which supposes that components are created as plain old Java objects is:

public class JavaLoggerBuilder implements LoggerBuilder {
 
  public void setLogger (Object component, String name, String level) {  
    LoggerFactory lf = Monolog.getDefaultMonologFactory();
    ((Loggable)component).setLogger(lf.getLogger(name));
  }
}

Factory component

The last step to use our new ADL module is to add our loader, compiler and builder components in the Fractal ADL factory component architecture. This can be done by defining a new MyFactory ADL definition that extends the org.objectweb.fractal.adl.BasicFactory definition, and a new MyFractalBackend ADL definition extending the org.objectweb.fractal.adl.FractalBackend definition. This factory can then be used like this:

Factory f = FactoryFactory.getFactory("MyFactory", "MyFractalBackend", ctxt);


(*) a node that can contain several sub nodes of the same type must be defined with an AST interface of the following form:

public interface ParentNode {
  SubNode[] getSubNodes();
  void addSubNode (SubNode subnode);
  void removeSubNode (SubNode subnode);
}