r


Fractal ADL Tutorial



AUTHOR :       
E. Bruneton          (France Telecom R&D)

Version   1.2
Released    March 10, 2004




1 Introduction

The Fractal Architecture Description Language (ADL) is an open and extensible language to define component architectures for the Fractal component model, which is itself open and extensible. More precisely, the Fractal ADL is made of an open and extensible set of ADL modules, were each module defines an abstract syntax for a given architectural "aspect" (such as interfaces, bindings, attributes, or containment relationships). Users are then free to define their own modules for their own aspects. They can also define their own concrete syntax for the language.

This tutorial presents the modules that are included in the Fractal ADL distribution, and uses the default XML based syntax for code samples (these code samples are available in the Fractal ADL distribution, in the "HelloWorld" example).

2 Primitive components

A primitive component can be defined by specifying the interfaces it provides, the interfaces it requires, and the class that implements this component:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE definition PUBLIC
    "-//objectweb.org//DTD Fractal ADL 2.0//EN"
    "classpath://org/objectweb/fractal/adl/xml/basic.dtd">


<definition name="ClientImpl">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <interface name="s" role="client" signature="Service"/>
  <content class="ClientImpl"/>
</definition>

The above definition defines the "ClientImpl" component. The header specifies the XML version, the encoding used, and the location of the basic Fractal ADL DTD. The first line inside "ClientImpl" defines a provided (or "server") interface, whose name is "r" and whose signature is "java.lang.Runnable". The second line defines a required (or "client") interface named "s", and the third line specifies that the component is implemented by the "ClientImpl" class (the name of the definition does not need to be the same as the class name, although this convention is frequently used).

Note: by default the cardinality and contingency of an interface is "singleton" and "mandatory", respectively. Other values can be specified like this:

<interface name="..." role="...." signature="..." cardinality="collection" contingency="optional"/>

3 Composite components

A composite component can be defined by specifying the interface it provides, the interfaces it requires, the sub components it contains, and the bindings between these sub components, and the composite component itself (XML headers are omitted in the rest of this document):

<definition name="HelloWorld">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <component name="client">
    <interface name="r" role="server" signature="java.lang.Runnable"/>
    <interface name="s" role="client" signature="Service"/>
    <content class="ClientImpl"/>
  </component>
  <component name="server">
    <interface name="s" role="server" signature="Service"/>
    <content class="ServerImpl"/>
  </component>
  <binding client="this.r" server="client.r"/>
  <binding client="client.s" server="server.s"/>
</definition>

The above definition defines the "HelloWorld" component. The first line defines a provided interface named "r". The following five lines define a primitive sub component named "client", which is equal to the primitive component introduced in the previous section. The following four lines define another primitive sub component named "server", with a single provided interface named "s". The last two lines define a binding from the internal client interface "r" of the composite component (designated by "this") to the server interface "r" of the client component, and a binding from the "s" client interface of the "client" component to the "s" server interface of the "server" component.

4 Component controllers

The controller of a primitive or composite component can be specified by defining a controller descriptor, as well as an attribute controller interface, and attribute values:

<definition name="ServerImpl">
  <interface name="s" role="server" signature="Service"/>
  <content class="ServerImpl"/>
  <attributes signature="ServiceAttributes">
    <attribute name="header" value="->"/>
    <attribute name="count" value="1"/>
  </attributes>
  <controller desc="primitive"/>
</definition>

The above definition specifies that the "ServerImpl" component has a "primitive" controller, and an attribute controller interface whose signature is "ServiceAttribute". It also defines values for the "header" and "count" attributes.

5 Component references

5.1 Simple references

A sub component of a composite component can be defined in two ways: either directly inside the definition of the composite component itself, or in another definition. In the last case, the composite component definition just contains a reference to the external definition:

<definition name="HelloWorld">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <component name="client" definition="ClientImpl"/>
  <component name="server">
    <interface name="s" role="server" signature="Service"/>
    <content class="ServerImpl"/>
  </component>
  <binding client="this.r" server="client.r"/>
  <binding client="client.s" server="server.s"/>
</definition>

Embedded definitions are more concise than external definitions, but they are less reusable. Using embedded definitions is therefore not recommended, except for definitions that are anyhow not reusable. Note that embedded and external definitions can be mixed arbitrarily.

5.2 Inheritance references

Component definitions can be defined by extending existing definitions. The extension mechanism is similar to class inheritance, i.e. a sub definition can add and override elements in its super definition. This mechanism can be used to define concrete components as sub definitions of abstract component definitions:

<definition name="ClientType">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <interface name="s" role="client" signature="Service"/>
</definition>

<definition name="ClientImpl" extends="ClientType">

  <content class="ClientImpl"/>
</definition>

The "ClientImpl" definition defined here is equivalent to the "ClientImpl" definition of section 2, because the above "ClientImpl" definition inherits the interfaces defined in "ClientType". This pattern can also be used for composite components:

<definition name="AbstractClientServer">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <component name="client" definition="ClientType"/>
  <component name="server" definition="ServerType"/>
  <binding client="this.r" server="client.r"/>
  <binding client="client.s" server="server.s"/>
</definition>

<definition name="ClientServerImpl" extends="AbstractClientServer">
  <component name="client" definition="ClientImpl"/>
  <component name="server" definition="ServerImpl"/>
</definition>

The "ClientServerImpl" definition is equivalent to the "HelloWorld" definition of section 3 because the "ClientServerImpl" definition overrides the "client" and "server" sub component definitions of "AbstractClientServer", and because it inherits the bindings defined in "AbstractClientServer".

A definition can be locally extended when defining a sub component by reference:

<definition name="ClientServerImpl">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <component name="client" definition="ClientType">
    <content class="ClientImpl"/>
  </component>
  <component name="server" definition="ServerType">
    <content class="ServerImpl"/>
  </component>
  <binding client="this.r" server="client.r"/>
  <binding client="client.s" server="server.s"/>
</definition>

The above definition is equivalent to the previous one, because the external definitions "ClientType" and "ServerType" are locally extended, in the "client" and "server" sub component definitions, which add class definitions to the component type definitions (these anonymous sub definitions are similar to anonymous inner classes in Java).

The "extends" and "definition" attributes can contain a comma separated list of definition names. In other words, multiple inheritance is allowed for component definitions. Conflicts are solved by systematically linearizing the inheritance graph. For example, if A1Type, A2Type, AType and BType are defined by:

<definition name="A1Type">
  <interface name="a" role="server" signature="A1"/>
</definition>
<definition name="A2Type">
  <interface name="a" role="server" signature="A2"/>
</definition>
<definition name="AType" extends="A1Type,A2Type"/>
<definition name="BType" extends="A2Type,A1Type"/>

then AType is equivalent to A2Type (because AType is equivalent to an AType extending an A2Type, itself extending the A1Type). Similarly, BType is equivalent to A1Type.

5.3 Shared components

Shared components are defined by using paths: in order for the components "a/server" and "b/server" to be physically equal (where "a/server" refers to the "server" sub component of the "a" sub component), one component must be defined as usual, and the other must be defined by giving the path from the currently defined component to the first one:

<definition name="SharedHelloWorld">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <component name="a" definition="HelloWorld"/>
  <component name="b" definition="HelloWorld">
    <component name="server" definition="a/server"/>
  </component>
  <binding client="this.r" server="a.r"/>
</definition>

The above definition defines a composite component named "SharedHelloWorld" that contains two sub components "a" and "b", which themselves contain a "client" and a "server" component (as defined in "HelloWorld"). However the definition of the "server" sub component of the "b" component has been overridden, and now refers to the "server" sub component of the "a" sub component of the currently defined component (i.e. SharedHelloWorld"). The effect of this definition is that "a/server" and "b/server" will be a single component shared by "a" and "b".

5.4 Mixed definitions

A component definition can refer to or extend a definition that is not defined with the same ADL modules, or with the same concrete syntax, so that independently developed components can still be composed together to build larger systems or applications (indeed, since the Fractal ADL is extensible, independently developed component definitions will likely use different modules and syntax). For example, a component defined by using the basic DTD can refer to a component type defined by using the core DTD, although the two DTDs do not include the same set of ADL modules):

ClientType.fractal:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE definition PUBLIC
    "-//objectweb.org//DTD Fractal ADL 2.0//EN"
    "classpath://org/objectweb/fractal/adl/xml/core.dtd">


<definition name="ClientType">
  <interface name="r" role="server" signature="java.lang.Runnable"/>
  <interface name="s" role="client" signature="Service"/>
</definition>

ClientImpl.fractal:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE definition PUBLIC
    "-//objectweb.org//DTD Fractal ADL 2.0//EN"
    "classpath://org/objectweb/fractal/adl/xml/basic.dtd">


<definition name="ClientImpl" extends="ClientType">
  <content class="ClientImpl"/>
</definition>

6 Arguments

The "arguments" module (only available with the "standard.dtd" DTD) provides the ability to define components with formal arguments, whose value can be fixed at load time, i.e. when the component definition is loaded in order to instantiate a corresponding component. Formal arguments must be defined in the "arguments" attribute of the "definition" element, and must be referenced with the "${...}" notation:

<definition name="GenericServerImpl" arguments="impl,header,count">
  <interface name="s" role="server" signature="Service"/>
  <content class="${impl}"/>
  <attributes signature="ServiceAttributes">
    <attribute name="header" value="${header}"/>
    <attribute name="count" value="${count}"/>
  </attributes>
  <controller desc="primitive"/>
</definition>

The scope of a formal argument is the lexical scope of the entire definition, i.e. it includes embedded component definitions, but does not extend to the externally referenced definitions. Arguments can be used anywhere inside this scope (such as in class names, attribute values, controller descriptors, or component references).

Definitions with formal parameters must be referenced by giving actual values to these arguments:

<definition name="ClientServerImpl" arguments="h" extends="AbstractClientServer">
  <component name="client" definition="ClientImpl"/>
  <component name="server" definition="GenericServerImpl(ServerImpl,${h},1)"/>
</definition>



The "arguments" module also provides the ability to define define default values for the definition arguments, whose value can be overridden at load time. Default values must be defined in the "arguments" attribute of the "definition" element using the "arg=value" notation:

<definition name="GenericServerImpl" arguments="impl=ServerImpl,header,count=3">
  <interface name="s" role="server" signature="Service"/>
  <content class="${impl}"/>
  <attributes signature="ServiceAttributes">
    <attribute name="header" value="${header}"/>
    <attribute name="count" value="${count}"/>
  </attributes>
  <controller desc="primitive"/>
</definition>

Thus, arguments defining a default value can be overridden by giving actual values to these arguments (as shown above) or by using the "arg=>value" notation:

<definition name="ClientServerImpl" arguments="h=>>" extends="AbstractClientServer">
  <component name="client" definition="ClientImpl"/>
  <component name="server" definition="GenericServerImpl(count=>1,header=>${h})"/>
</definition>

7 Other modules

The comments module (only available with the "standard.dtd" DTD) provides the ability to add human readable comments to any element of a definition. Several comments can be defined for the same element, and a language can be specified for each comment:

<definition name="HelloWorld">
  <comment text="definition for the HelloWorld composite component"/>
  <comment language="fr" text="definition pour le composant composite HelloWorld"/>
  <interface name="r" role="server" signature="java.lang.Runnable">
    <comment text="the main interface of the composite component"/>
  </interface>
  <component name="client">
    <interface name="r" role="server" signature="java.lang.Runnable"/>
    <interface name="s" role="client" signature="Service"/>
    <content class="ClientImpl">
      <comment text="the client component is implemented by the ClientImpl Java class"/>
    </content>
  </component>
  <component name="server">
    <interface name="s" role="server" signature="Service"/>
    <content class="ServerImpl"/>
  </component>
  <binding client="this.r" server="client.r">
    <comment text="defines an export binding"/>
  </binding>
  <binding client="client.s" server="server.s"/>
</definition>

TODO logger module (not yet implemented)
TODO distribution module (not yet implemented)
TODO coordinates module (not yet implemented)

8 Instantiation

In order to instantiate a component from an ADL definition, one first needs to get a factory that can create components from ADL definitions. This can be done in the following way:

Factory f = FactoryFactory.getFactory();

were Factory and FactoryFactory are defined in the org.objectweb.fractal.adl package. The factory can then be used to create the component:

Object c = f.newComponent("ClientImpl", null);

8.1 Definition sources

The default factory looks for definitions in the classpath: a definition "org.pkg.Foo" is loaded by looking for the "org/pkg/Foo.fractal" resource with the getResourceAsStream method of the ClassLoader of the Factory component.

As a consequence, a definition "org.pkg.Foo" must be defined in a file "Foo.fractal" in the "org/pkg" directory, which must be accessible (either directly or inside a jar file) from the class loader of the Factory component.

8.2 Definition arguments

The newComponent method of the Factory interface takes as parameter the name of a definition, and an optional Map that can be used to give additional information to the factory. For example, this map is used by the "arguments" module to look for actual argument values:

Map context = new HashMap();
context.put("impl", "ServerImpl");
context.put("header", "->");
context.put("count", "1");
f.newComponent("GenericServerImpl", context);

8.3 Backends

The default Fractal ADL factory is a Fractal composite component (defined with Fractal ADL definitions) made of many components, including a backend component that comes in four versions: a Java backend, a Fractal backend, a static Java backend, and a static Fractal backend.

The Java backend (which is the default backend) creates components by using the java.lang.reflect API. For example, this backend creates the "ClientImpl" component with a code similar to Class.forName("ClientImpl").newInstance().

The Fractal backend creates components by using the Fractal API. This backend uses the Fractal.getBootstrapComponent() bootstrap component, unless another bootsrap component is associated to the "bootstrap" key in the factory context. For example:

Map context = new HashMap();
context.put("bootstrap", /* a [remote] bootstrap component */); // optional
Factory f = FactoryFactory.getFactory(FactoryFactory.FRACTAL_BACKEND);
Component c = (Component)f.newComponent("ClientImpl", context);

creates c by executing the following code:

Component boot = (Component)context.get("bootstrap");
TypeFactory tf = Fractal.getTypeFactory(boot);
GenericFactory gf = Fractal.getGenericFactory(boot);
ComponentType t = tf.createFcType(new InterfaceType[] {
  tf.createFcItfType("r", "java.lang.Runnable", false, false, false),
  tf.createFcItfType("s", "Service", true, false, false)
});
Component c = gf.newFcInstance(t, "primitive", "ClientImpl");

The static Java and Fractal backends are similar to the Java and Fractal backends, except that they produce source code instead of directly interpreting it. These two backends require a PrintWriter associated to the "printwriter" key in the factory context. This PrintWriter is the output stream that will be used to generate the source code. For example:

Map context = new HashMap();
context.put("printwriter", new PrintWriter(...));
Factory f = FactoryFactory.getFactory(FactoryFactory.STATIC_JAVA_BACKEND);
String s = (String)f.newComponent("HelloWorld", context);

generates source code similar to:

ServerImpl P0 = new ServerImpl();
P0.setHeader("->");
P0.setCount(1);
ClientImpl P1 = new ClientImpl();
P1.bindFc("s", P0);
Map C0 = new HashMap();
C0.put("r", P1);

and returns the identifier of the top level component, i.e. the "C0" string here (composite components are represented by maps that associate a value to each interface name of the component). In order to be really usable, the generated code must be enclosed in a complete Java class. This must be done by the Fractal ADL user.
Copyright © 1999-2005, ObjectWeb Consortium | contact | webmaster | Last modified at 2012-12-03 09:57 PM