DO NOT REMOVE THIS LINE (will be automatically removed).
DO NOT REMOVE THIS LINE (will be automatically removed).
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 parses concrete ADL definitions, and builds the
corresponding ASTs. It can also optionally verify semantic constraints,
and perform transformations on the ASTs.
- the compiler tool takes an AST as input, and outputs the set of
tasks that are needed to create the components decribed in the AST, as
well as the dependencies between tasks.
- the builder tool defines a concrete behavior for the tasks
generated by the compiler tool. In fact this tool comes in four
versions, to create components with the Java API or the Fractal API, or
to produce Java or Fractal source code to create the components,
instead of creating them directly.
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 "get
Xyz" 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);
}