Julia API Documentation

See:
          Description

Packages
org.objectweb.fractal.julia Provides mixins to implement the org.objectweb.fractal.api interfaces.
org.objectweb.fractal.julia.asm Provides some class generators based on ASM.
org.objectweb.fractal.julia.control.attribute Provides mixins to implement the AttributeController interface.
org.objectweb.fractal.julia.control.binding Provides mixins to implement the BindingController interface.
org.objectweb.fractal.julia.control.content Provides mixins to implement the ContentController interface.
org.objectweb.fractal.julia.control.lifecycle Provides mixins to implement the LifeCycleController interface.
org.objectweb.fractal.julia.control.name Provides mixins to implement the NameController interface.
org.objectweb.fractal.julia.factory Provides mixins to implement the [Generic]Factory interfaces.
org.objectweb.fractal.julia.loader Provides a loader to load or to generate classes on the fly.
org.objectweb.fractal.julia.logger Provides a mixin to assign a Logger to components when they are started.
org.objectweb.fractal.julia.type Provides mixins to implement the TypeFactory interface.
org.objectweb.fractal.juliak Fractal provider for component-based control membranes.
org.objectweb.fractal.juliak.asm Provides some class generators based on ASM for component-based control membranes.
org.objectweb.fractal.juliak.control.attribute Provides the definition of the AttributeController control component.
org.objectweb.fractal.juliak.control.binding Provides the definition of the BindingController control component.
org.objectweb.fractal.juliak.control.component Provides the definition of the Component control component.
org.objectweb.fractal.juliak.control.content Provides the definition of the ContentController and the SuperController control components.
org.objectweb.fractal.juliak.control.factory Provides the definition of the Factory control component.
org.objectweb.fractal.juliak.control.interceptor This package contains the definition of the interceptor control component.
org.objectweb.fractal.juliak.control.lifecycle Provides the definition of the LifeCycleController control component.
org.objectweb.fractal.juliak.control.membrane Provides the definition of the MembraneController control component.
org.objectweb.fractal.juliak.control.name Provides the definition of the NameController control component.
org.objectweb.fractal.juliak.factory This package contains the implementations of Fractal factories.
org.objectweb.fractal.juliak.membrane This package contains the Fractal ADL definitions of control membranes.
org.objectweb.fractal.juliak.platform This package contains the code which is platform (e.g.

 

Table of Contents

1 Introduction
2 Design goals
3 Code structure
4 Main data structures
5 Mixin classes
6 Interceptor classes
7 Class generation framework
8 Support for constrained environments
9 Component-based control membranes
A Mixins reference
B Optimizations

1 Introduction

Julia is France Telecom's implementation of the Fractal Specification. This document describes the design choices made to implement Julia, the organization of Julia's source code, and the main algorithms and data structures used in Julia. This document is intended for framework developers that want to extend Julia, but it can also be useful for component developers that want to understand how Julia works. The reader is supposed to be familiar with the Fractal Specification.

2 Design Goals

The main design goal is to implement a framework to program component controllers: we want to implement an extensible set of control objects, from which the user can freely choose and assemble the controller objects he or she wants, in order to build the controller part of a Fractal component.

The second goal is to provide a complete continuum from static configuration to dynamic reconfiguration: we want to provide a sufficiently rich and flexible set of control objects, so that the user can make the speed/memory tradeoffs he or she wants. In particular, this set of control objects must allow the user to instantiate non reconfigurable but very efficient components or, on the contrary, completely reconfigurable but less efficient components. It must also allow the user to mix these components, in order to use different optimization levels for the different parts of an application. The last requirement is to be able to dynamically deoptimize an optimized component, in order to make it reconfigurable, and to be able to dynamically reoptimize it after it has been reconfigured.

The third goal is to implement these control objects so as to minimize the time overhead due to these objects on user applications and to optimize the performances of the methods of the Fractal API. The memory overhead of the controller objects was not a priority (indeed, in many cases, the number of components is less than one hundred or a thousand of components).

The last goal is to implement a framework that can be used on any JVM and/or JDK, including very constrained ones such as the KVM and the J2ME profile (where there is no ClassLoader class, no reflection API, no collection API...).

In addition to the previous design goals, we also make two hypotheses in order to simplify the implementation:

3 Code Structure

The source code of Julia is organized in three parts: By default, when a Fractal application is launched with Julia, the generator classes dynamically generate the needed application specific classes, as well as the needed controller classes (by mixing some or all of the mixins classes). These classes are then used to construct the application's components. If these generated classes are stored on disk and included in the classpath, then the application can be constructed directly, without needing the generator and mixins classes. In summary, Julia can be used in two different ways: The Julia source is organized in several packages that have the same structure as the Fractal API packages:

4 Main Data Structures

This section presents an overview of the data structures used in Julia to represent Fractal components.

4.1 Overview

Figure 1: an abstract component and a possible implementation in Julia

A Fractal component is generally represented by many Java objects, which can be separated into three groups (see Figure 1):

The fact that each component interface is represented by its own Java object comes from the fact that component interfaces are typed (i.e., a component interface object implements both Interface and the Java interface corresponding to this interface). It is not possible to do better, unless perhaps by using very complex bytecode manipulations that modify the signature of all the methods of all classes (if i and j are interface objects corresponding to two component interfaces named "iitf" and "jitf", then i.getFcName() = "iitf" and j.getFcItfName() = "jitf". i and j must therefore be distinct objects - if they were physicaly equal, then i.getFcItfName() and j.getFcItfName() would be logically equal too).

The objects that represent the controller part of a component can be separated into two groups: the objects that implement the control interfaces (in black in the above figure), and (optional) interceptor objects (in brown) that intercept incoming and/or outgoing method calls on functional (or business, or user) interfaces. These objects implement respectively the Controller and the Interceptor interfaces. Each controller object can contain references (in black) to other controller objects (since the control aspects are generally not independent - or "orthogonal" - they must generally communicate between each other, and must therefore have references to each other).

The objects that implement the content part of a component can be sub components (for composite components), or user objects (for primitive components). Note: Julia makes a distinction between the primitive components that contain no user objects (such as primitive template components), which are just called "primitive", and primitive components that contain a non empty rooted graph of user objects, which are called "container" (this name can lead to confusions with the fuzzy EJB and CCM container definitions, but we do not have a better name to express the fact that these components contain a non empty set of user objects).

4.2 Constraints

In order to reconfigure a component, it is sometimes necessary to replace an object by another (for example to change an interceptor). The problem is that, in order to do so, all the Java references to the object that must be replaced must be changed also. And, in general, these references are not known. In order to be able to reconfigure Fractal components, Julia makes the following hypotheses: These hypotheses eliminate the unknowable references to controller and content objects (i.e. the references from user objects to controller and content objects, which can not be known from Julia), which can therefore be replaced if necessary. Indeed, although there can be references between controller objects, between component interface and controller objects, between interceptor and "content" objects, and so on, all these references are managed by Julia, and are therefore known.

These hypotheses also explain why the component interface objects are separated from controller and interceptors objects: this is to be able to change the controller and interceptor objects.

(*) the only exception is when user objects are bound directly to each other, in a "static" configuration (see section 2.4 in the Julia tutorial). In this case there may be unknowable references to content objects (but not to controller or interceptor objects). In this case the application must not disseminate these references, or must know and update them all when requested to do so (via BindingController), or else there may be problems when a user component is replaced by another.

4.3 Hidden Interfaces

Since the control aspects are generally not independent - or "orthogonal" - they must generally communicate between each other, either inside a single component, or between components. In these cases, in order to have a modular framework, the interface and implementation separation principle is used (*): if a controller object needs a service provided by another one, a Java interface is defined for this service, which is implemented by the server object, and used in the client object.

In order to be able to add protection mechanisms to Julia, or to be able to provide distributed components on top of Julia (as Fractal RMI does), it was also decided that the previous interfaces, only used inside Julia but not visible from the outside, would be retrieved like any other component interface, i.e., via the getFcInterface method of the Component interface. The only difference is that these interfaces are hidden, i.e., they do not appear in the array returned by getFcInterfaces (hidden interfaces are distinguished from normal interfaces by the fact that their name starts with "/"). Therefore, one cannot get the reference to such an interface without knowing its name. And, although not yet implemented, it is also possible to perform access control checks in the getFcInterface method, to ensure that only Julia's classes can access these hidden interfaces.

(*) a reference between an interceptor and a controller object may not follow this principle, for performance reasons. For example, the lifecycle interceptor object directly uses fields defined in the lifecycle controller object. In practice this is not a problem, because the algorithms used in these two objects are strongly coupled (i.e., the interceptor can not be changed without changing the controller object, and vice versa).

4.4 Instantiation

Julia components can be created manually or automatically. The manual method can be used to create any kind of components, while the automatic one is restricted to components whose type follows the basic type system defined in the Fractal specification, which provide a Component interface, and which provide interface introspection functions. In both methods, a component must be created as follows: In the automatic method, i.e. when components are created through the GenericFactory interface, the operations that must be done at the previous steps are deduced from the component's type, and from its controller and content descriptor. Once these descriptors have been analyzed and checked, and once the previous operations have been determined, a sub class of the InitilizationContext class that implements these operations is generated (with the InitializationContextClassGenerator). Finally the component is created by using this generated class (in other words, the controller are content descriptors are compiled on the fly, once and for all, instead of being interpreted and checked each time a component must be created).

5 Mixin classes

5.1 Motivations

The main design goal of Julia is to implement a framework to program component controllers (see section 2). In particular, since everything in the Fractal specification is optional, Julia must provide implementations of the Fractal API interfaces for any conformance level (see the Fractal specification). For example, Julia must provide a basic Component implementation, as well as an implementation for components whose type follows the basic type system (in the first case Component behaves like a read only hash map; in the second case, because of collection interface types, the getFcInterface method can lazily create new component interfaces). Likewhise, Julia must provide a basic BindingController implementation, as well as an implementation for cases where the basic type system is used, where a life cycle controller is present, or where composite components are used (these implementations are needed to check type, life cycle or content related constraints on bindings). There must also be an implementation for cases where both the basic type system and a life cycle controller are used, or where both the basic type system, life cycle controllers and composite components are used. And these implementations must be extensible, in order to take into account user defined controllers when needed.

In order to provide all these implementations, a first solution would be to use class inheritance. But this solution is not feasible, because it leads to a combinatorial explosion, and to a lot of code duplication. Consider for example the BindingController interface, and the "type system", "life cycle" and "composite" concerns. These three concerns give 23=8 possible combinations. Therefore, in order to implement these three concerns, eight classes (and not just three) must be provided. Moreover these eight classes can not be provided without duplicating code, if multiple inheritance is not available.

Another solution to this problem would be to use an Aspect Oriented Programming (AOP) tool or language, such as AspectJ, since the goal of these tools and languages is to solve the "crosscutting" problems. AspectJ, for example, could effectively be used to solve the above problem: three aspect classes are sufficient to implement the three concerns, without any code duplication. But using AspectJ would introduce a new problem, due to the fact that, in AspectJ, aspects must be applied at compile time, and that this process requires the source code of the "base" classes. It would then be impossible to distribute Julia in compiled form, in a jar file, because then the users would not be able to apply new aspects to the existing Julia classes (in order to add new control aspects that crosscut existing ones).

What is needed to really solve our modularity and extensibility problem is therefore a kind of AOP tool or language that can be used at load time or at runtime, without needing the source code of the base classes, such as JAC (Java Aspect Components). For performance reasons the current Julia version does not use JAC or other similar systems: it uses instead some kind of mixin classes. A mixin class is a class whose super class is specified in an abstract way, by specifying the minimum set of fields and methods it should have. A mixin class can therefore be applied (i.e. override and add methods) to any super class that defines at least these fields and methods. This property solves the above combinatory problem. The AspectJ problem is solved by the fact that the mixin classes used in Julia can be applied at runtime (unlike in most mixin based inheritance languages, where mixed classes are declared at compile time).

5.2 Implementation

Instead of using a Java extension to program the mixin classes, which would require an extended Java compiler or a pre processor, mixin classes in Julia are programmed by using patterns. For example a mixin class, which, in JAM, would be written as:
mixin A {

  inherited public void m ();

  public int count;
  public void m () {
    ++count;
    super.m();
  }
}
is written in Julia in pure Java, as follows:
abstract class A {

  abstract void _super_m ();

  public int count;
  public void m () {
    ++count;
    _super_m();
  }
}
In other words, the _super_ prefix is used to denote the inherited members in JAM, i.e., the members that are required in a base class, for the mixin class to be applicable to it. More precisely, the _super_ prefix is used to denote methods that are overridden by the mixin class. Members that are required but not overridden are denoted with _this_ (a mixin class cannot contain a _super_m method if it does not have a corresponding m method. Likewise, a mixin class cannot have both a _this_m method and a corresponding m method):
abstract class M implements I, Countable {

  int _this_f;
  abstract void _super_m ();
  abstract void _this_n ();

  public int count;
  public void m () {
    ++count;
    _this_n();
    _super_m();
  }
  public int getCount () {
    return count;
  }
}
Mixin classes can be mixed, resulting in normal classes. More precisely, the result of mixing several mixin classes M1, ... Mn, in this order, is a normal class that is equivalent to a class Mn extending the Mn-1 class, itself extending the Mn-2 class, ... itself extending the M1 class. Several mixin classes can be mixed only if each method and field required by a mixin class Mi is provided by a mixin class Mj, with j < i (each required method and field may be provided by a different mixin). For example, if N and O designate the following mixins:
abstract class N implements I {

  abstract void _super_m ();
  abstract void _super_n ();

  public void m () {
    System.out.println("m called");
    _super_m();
  }
  public void n () {
    System.out.println("n called");
    _super_n();
  }
}

abstract class O implements I {
  public int f;
  public void m () {
    System.out.println("m");
  }
  public void n () {
    System.out.println("n");
  }
}
then the mixed class O N M is equivalent to the following class (note that this class implements all the interfaces implemented by the mixin classes):
public class ONM implements I, Countable {

  // from O
  public int f;
  private void m$1 () {
    System.out.println("m");
  }
  private void n$0 () {
    System.out.println("n");
  }

  // from N
  private void m$0 () {
    System.out.println("m called");
    m$1();
  }
  public void n () {
    System.out.println("n called");
    n$0();
  }

  // from M
  public int count;
  public void m () {
    ++count;
    n();
    m$0();
  }
  public int getCount () {
    return count;
  }
}
while the mixed class O M N is the following class:
public class OMN implements I, Countable {

  // from O
  public int f;
  private void m$1 () {
    System.out.println("m");
  }
  private void n$0 () {
    System.out.println("n");
  }

  // from M
  public int count;
  private void m$0 () {
    ++count;
    n();
    m$1();
  }
  public int getCount () {
    return count;
  }

  // from N
  public void m () {
    System.out.println("m called");
    m$0();
  }
  public void n () {
    System.out.println("n called");
    n$0();
  }
}
The mixed classes are generated dynamically by the MixinClassGenerator class. Since this class can not mix constructors (this limitation could be removed in future versions), a mixin class must not rely on constructors to initialize its instances (in Julia, all mixin classes have a private empty constructor without argument, to show that mixin classes should not be instantiated directly). A mixed class can be declared as follows in the Julia configuration file:
(onm
  (org.objectweb.fractal.julia.asm.MixinClassGenerator
    ONM
    org.pkg.O
    org.pkg.N
    org.pkg.M
  )
)
The first line after MixinClassGenerator is a symbolic name for the mixed class. The following lines are the names of the classes to be mixed. In order to ease debugging, the class generator keeps the line numbers of the mixin classes in the mixed class. More precisely, a line number l of the mixin class at index i (in the list of mixin classes, and starting from 1) is transformed into 1000*i + l. For example, if a new Exception().printStackTrace() were added in the N.m method, the stack trace would contain the following line:

at C55d992cb_0.m$0(ONM:2010)

meaning that the exception was created in method m$0 of the C55d992cb_0 class, whose source is the ONM mixed class (ONM comes from the first line after MixinClassGenerator in the above descriptor), at line 10 of mixin 2, i.e. at line 10 of the org.pkg.N mixin class.

6 Interceptor classes

This section gives some details about the generator used to generate interceptor classes. This generator takes as parameters the name of a super class, the name(s) of one or more application specific interface(s), and one or more "aspect" code generator(s). It generates a sub class of the given super class that implements all the given application specific interfaces and that, for each application specific method, implements all the "aspects" corresponding to the given "aspect" code generators.

Each "aspect" code generator can modify the code of each application specific method arbitrarily. For example, an "aspect" code generator A can modify the method void m () { impl.m() } into:


void m () {
  // pre code A
  try {
    impl.m();
  } finally {
    // post code A
  }
}
while another "aspect" code generator B will modify this method into:

void m () {
  // pre code B
  impl.m();
  // post code B
}
When an interceptor class is generated by using several "aspect" code generators, the transformations performed by these code generators are automatically composed together. For example, if A and B are used to generate an interceptor class, in this order, the result for the previous m method is the following:

void m () {
  // pre code A
  try {
    // pre code B
    impl.m();
    // post code B
  } finally {
    // post code A
  }
}
The order in which the aspects are "woven" is generally important, and must be specified by the user of the code generator. For example, if A and B are used in the reverse order, the result for the previous m method is the following:

void m () {
  // pre code B
  // pre code A
  try {
    impl.m();
  } finally {
    // post code A
  }
  // post code B
}
Note that, thanks to this (elementary) automatic weaving, which is very similar to what can be found in AspectJ or in Composition Filters, several aspects can be managed by a single interceptor object: there is no need, and there are no chains of interceptor objects, each object corresponding to an aspect (if one really wants interceptor chains, this can be done by implementing a new interceptor class generator).

Like the controller objects, the aspects managed by the interceptor objects of a given component can all be specified by the user when the component is created. The user can therefore not only choose the control interfaces he or she wants, but also the interceptor objects he or she wants. Julia only provides two aspect code generators: one to manage the lifecycle of components, the other to trace incoming and/or outgoing method calls. Julia also provides two abstract code generators, named SimpleCodeGenerator and MetaCodeGenerator, that can be easily specialized (i.e. without needing to know ASM), in order to implement custom code generators.

Note: each aspect code generator is given a Method object corresponding to the method for which it must generate interception code. Thanks to this argument, a code generator can generate code that adapts to the specific signature of each method. It can also generate code to reify the method's parameters if desired (although this is less efficient than the first method).

7 Class Generation Framework

Julia needs a lot of automatically generated classes, such as the component factory classes (see section 4.4), the controller classes generated from the mixin classes defined in Julia (see section 5), the classes that implement both Interface and an application specific interface, the interceptor classes (see section 6), the merged controller classes (see appendix B), and so on.

Since Julia must work even with very limited JVMs (see section 2), without application specific class loaders, and without the Java reflection API, the generators that generate the previous classes must be useable either dynamically or statically, before launching the application. A convention is therefore needed to name the generated classes. Indeed, in the static case, the "generated" classes must be loaded from the classpath and, to this end, their name must be known. And, therefore, the static class generator cannot give arbitrary names to the class it generates.

A solution to this problem is to name the generated classes with the parameters that were used to generate them. Indeed, in this case, when a generated class is needed at runtime, and since the parameters to generate it are of course known at this time, the class can either be dynamically generated with these parameters or, in the static case, loaded with Class.forName method, by using these parameters as a class name.

The problem of this solution is that the parameters used to generate a class are quite long, and this would result in very long, and may be too long, generated class names (some JVMs do not accept classes with a name of more than 256 characters). In order to solve this problem, the name of a generated class is the hashcode of the parameters that were used to generate this class, instead of being a bijective encoding of these parameters. Of course, two different sets of parameters can have the same hashcode, and so conflicts are possible. To solve this new problem, each generated class contains a getFcGeneratorParameters method, defined in the Generated interface, whose role it to return the exact parameters that were used to generate the class. The algorithm to load a generated class is then the following:


hashcode = hashcode(parameters needed to generate the desired class);
int n = 0;
while (true) {
  Class c = Class.forName("package." + hashcode + "_" + n);
  Generated g = (Generated)c.newInstance();
  if (g.getFcGeneratorParameters().equals(above parameters)) {
    return c;
  }
  ++n;
}
Notes:

8 Support for Constrained Environments

As explained in section 2, one of the goals of Julia is to be usable even with very constrained JVMs and JDKs, such as the KVM and the J2ME libraries (CLDC profile). This goal is achieved thanks to the following properties:

9 Component-based Control Membranes

Since version 2.5, the control membrane of a component can be implemented with an assembly of so-called control components. This feature which were originally developed for AOKell, has been ported to Julia. This new feature does not modify any of the existing elements of the Julia framework: existing applications run unmodified and the various features of the framework up to advanced ones such as intra and inter components optimizations (see appendix B) are still supported without any modification.

The code related to the implementation of component-based control membranes (CBCM) can be found in the org.objectweb.fractal.juliak package and its sub packages. A new Fractal provider class, org.objectweb.fractal.juliak.Juliak, is available. When used, this class provides a component factory which instanciates application-level components with component-based membranes. This class can coexist with the original Fractal provider class org.objectweb.fractal.julia.Julia. Both provider classes can be instanciated and used jointly in a same JVM. The components created by the generic factories of these providers are fully-compatible (they implement the same control semantics) and can be assembled together in a same application.

9.1 Rationale

The rationale for using component-based control membrane is to be able to (re)configure the control part of a Fractal component. This control part (the membrane) is a component (a so-called control component) which exports some control interfaces. Control components conform to the Fractal API. They can be introspected and dynamically reconfigured.

No restriction is put on the implementation of a control membrane: this can be a simple primitive component, or this can be a more complex composite component which contains several levels of sub-components with arbitrary bindings. The idea behind implementing the membrane as an assembly of control components stems from the fact that controllers are not isolated entities. For example, the standard Julia binding controller for composite components uses the services provided by the content controller, the lifecycle controller and the controller implementing the Component interface. Appendix A lists the dependencies which exist between standard Julia controllers: each dependency can be deduced by the presence of a mixin layer whose name starts with Use. By reifying these dependencies with a Fractal component-based architecture, we are able to dynamically (re)configure the control part of a component.

The general idea underlying CBCM is summarized in Figure 2. An application-level component can be equipped with an assembly of control components. Each control component implements the functionalities of a control interface. Note however that the use of component-based control membranes is not mandatory. Developers can still use regular object-based control layers. Furthermore, component-based and object-based membranes can coexist in a same assembly of application-level components.

component-based control membrane

Figure 2: component-based control membrane

The use of component-based control membrane does not modify the mixin mechanism which is used in Julia for generating controller implementations. Control components have a content class. This can be a plain old Java class or a class outputted by any generative programming mechanism such as the mixin algorithm.

A component-based version of the 15 standard control membranes (bootstrap, primitive, composite, parametricPrimitive, parametricComposite, primitiveTemplate, compositeTemplate, parametricPrimitiveTemplate, parametricCompositeTemplate, autoBindingPrimitive, autoBindingComposite, flatPrimitive, flatParametricPrimitive, flatPrimitiveTemplate, flatParametricPrimitiveTemplate) existing in Julia has been defined. The Fractal-ADL architecture description of these membranes can be found in the org.objectweb.fractal.juliak.membrane package. The implementation of the control components used by these membranes is the same as the one used for the object-oriented membranes: their code is generated with the mixin layers defined in the org.objectweb.fractal.julia package and its sub packages.

9.2 Control membrane ADL description

This section illustrates the definition of a control membrane with Fractal ADL. We take the example of a primitive membrane which is the most commonly used membrane type.

<!-- o.o.f is a shortcut for org.objectweb.fractal -->
<definition name="o.o.f.juliak.membrane.Primitive"
 extends="o.o.f.juliak.control.component.ComponentControllerType,
          o.o.f.juliak.control.binding.BindingControllerType,
          o.o.f.juliak.control.lifecycle.LifeCycleControllerType,
          o.o.f.juliak.control.name.NameControllerType,
          o.o.f.juliak.control.content.SuperControllerType,
          o.o.f.juliak.control.interceptor.InterceptorControllerType,
          o.o.f.juliak.control.membrane.MembraneControllerType" >

  <component name="Comp"
   definition="o.o.f.juliak.control.component.ComponentController" />

  <component name="BC"
   definition="o.o.f.juliak.control.binding.ContainerBindingController" />

  <component name="LC"
   definition="o.o.f.juliak.control.lifecycle.LifeCycleController" />

  <component name="NC"
   definition="o.o.f.juliak.control.name.NameController" />

  <component name="SC"
   definition="o.o.f.juliak.control.content.SuperController" />

  <component name="IC"
   definition="o.o.f.juliak.control.lifecycle.LifeCycleInterceptor" />

  <component name="MC"
   definition="o.o.f.juliak.control.membrane.MembraneController" />

  <binding client="this.//component" server="Comp.//component" />
  <binding client="this.//binding-controller" server="BC.//binding-controller" />
  <binding client="this.//lifecycle-controller"
           server="LC.//lifecycle-controller" />
  <binding client="this.//name-controller" server="NC.//name-controller" />
  <binding client="this.//super-controller" server="SC.//super-controller" />
  <binding client="this.///interceptor-controller"
           server="IC.///interceptor-controller" />

  <binding client="BC.//component" server="Comp.//component" />
  <binding client="BC.//super-controller" server="SC.//super-controller" />
  <binding client="BC.//lifecycle-controller"
           server="LC.//lifecycle-controller" />
  <binding client="LC.//component" server="Comp.//component" />
  <binding client="IC.//component" server="Comp.//component" />
  <binding client="IC.//lifecycle-controller"
           server="LC.//lifecycle-controller" />

  <controller desc="mComposite" />
</definition>

 

This membrane contains 7 sub components, exports the server interfaces of these components, and defines 6 bindings between them. Of course, such an architecture stems from the chosen control semantics for primitive components. Two pieces of data in such a definition are specific to CBCM:

9.3 Component-based membrane specific features

Four features are specific to CBCM:

9.3.1 Introspection of a control membrane

CBCM are regular assemblies of Fractal components. They can be introspected with the Fractal API: component hierarchies can be traversed and modified, interfaces can be retrieved, and bindings can be queried, set and removed. By this way, one is able to dynamically modify at run-time the control policy attached to an application-level component. The consistency of the modification is under the responsibility of the developer: s/he must take care that the performed changes do not hinder the currently executing component.

In order to introspect a control membrane, the developer needs to retrieve the reference of the Component instance which plays this role. For that, every component created by the Juliak Fractal provider class is equipped with a control interface named /membrane-controller. This Fractal interface implements the MembraneController Java interface which provides the following method:

public Component getFcMembrane();

This method returns the reference of the control membrane associated to the current application-level component. A typical sequence of code for introspecting a control membrane will then starts with:

Component myComp = ...
MembraneController mc = (MembraneController)
    myComp.getFcInterface("/membrane-controller");
Component membrane = mc.getFcMembrane();

9.3.2 Gluing of a control membrane to a content

The component factory provided by the Juliak Fractal provider class implements the standard GenericFactory interface. This interface provides the newFcInstance method which returns a component instance. Three parameters must be provided: a Type to describe the requested instance, a controller descriptor and a content descriptor. In regular cases, the Type is a ComponentType, the controller descriptor is a string such as primitive or composite which designates the type of the requested membrane, and the content descriptor is the fully-qualified name which corresponds to the class implementing the content of the component (may be null when a composite component is requested).

In addition to this regular usage pattern of the Juliak Fractal provider class, the controller descriptor may be a Component instance. This Component instance is taken to be the control part for the created application-level component. This instance is a regular component which may be obtained by any mean provided by Fractal to generate a component. For example, it may be loaded from a Fractal-ADL description, created by the generic factory or a template, or assembled with the API from existing parts. Two conditions must be met by this Component instance to be a legal control membrane:

In addition, only server interfaces with a // prefix are considered to be control interfaces for the application-level component.

9.3.3 Dynamic management of interceptors

An interceptor controller is provided by CBCM to be able to dynamically add and remove interceptors to a component. The consistency of the adding and the removal of an interceptor is under the responsibility of the developer who performs the change. The interceptor controller implements the InterceptorController interface.

The interceptor controller is defined as follows:

<!-- o.o.f is a shortcut for org.objectweb.fractal -->
<definition name="o.o.f.juliak.control.interceptor.InterceptorController"
 extends="o.o.f.juliak.control.component.UseComponentControllerType" >

  <interface
    name="///interceptor-controller"
    signature="o.o.f.juliak.control.interceptor.InterceptorController"
    role="server"
  />

  <content
    class="o.o.f.juliak.control.interceptor.InterceptorCompControllerImpl" />

  <controller desc="mPrimitive" />
</definition>

An interceptor controller is a mPrimitive control component which exports a /interceptor-controller interface and which is implemented by the InterceptorCompControllerImpl. Note also that this controller must be bound to the Component controller.

The definition of the interceptor controller can  be extended to specify custom code generators for interceptors. This is the case of the LifeCycleInterceptor control component which is included in the definition of a Primitive membrane. Its definition follows:

<!-- o.o.f is a shortcut for org.objectweb.fractal -->
<definition name="o.o.f.juliak.control.lifecycle.LifeCycleInterceptor"
 extends="o.o.f.juliak.control.interceptor.InterceptorController,
          o.o.f.juliak.control.lifecycle.UseLifeCycleControllerType" >

  <attributes
    signature="o.o.f.juliak.control.interceptor.InterceptorDefAttributes">
    <attribute
      name="interceptors"
      value="(o.o.f.julia.asm.InterceptorClassGenerator
              o.o.f.julia.asm.LifeCycleCodeGenerator)"
    />
  </attributes>

</definition>

The interceptors attribute in the previous piece of Fractal-ADL code specifies the class generator description which is attached to the LifeCycleInterceptor control component. The syntax of this description follows the Lisp-like style which is used in julia.cfg files. When a control membrane containing such an interceptor control component is bound to a content instance, interceptors are generated and attached to the interfaces of the component.

9.3.4 Joint use of object-oriented and component-based control membrane

As said previously, the generic factory associated with the org.objectweb.fractal.juliak.Juliak Fractal provider class instanciates components equipped with a component-based control membrane. The membrane is choosen according to the value of the controller descriptor parameter sent to the newFcInstance method. This parameter is a string such as primitive or composite.

As an additional feature, the generic factory associated with the org.objectweb.fractal.juliak.Juliak Fractal provider class provides the ability to instanciate components equipped with an object-oriented membrane. For that, a naming convention is adopted which consists in inserting the /julia/ prefix in front of controller descriptors. For example, the programmer may request a /julia/primitive or a /julia/composite component. In such cases, this factory delegates the instanciation to the factory associated with the org.objectweb.fractal.julia.Julia Fractal provider class.

As a matter of example, the following piece of code create one composite and two primitive components. The primitive ones are inserted into the composite and are bound together. The composite and the second primitive are equipped with a component-based membrane. The first primitive is equipped with an object-oriented membrane (see the second parameter of the call to newFcInstance on line 2).

Component root = cf.newFcInstance(rType,"composite",null);
Component cComp = cf.newFcInstance(cType,"/julia/primitive,"ClientImpl");
Component sComp = cf.newFcInstance(sType,"primitive,"ServerImpl");

Fractal.getContentController(root).addFcSubComponent(cComp);
Fractal.getContentController(root).addFcSubComponent(sComp);

Fractal.getBindingController(root).bindFc("m",cComp.getFcInterface("s"));
Fractal.getBindingController(cComp).bindFc("s",sComp.getFcInterface("s"));

By default, components with object-oriented and component-based membranes implement the same control semantics and are thus fully compatible.


A Mixins reference

A.1 Mixins for the Component interface

Figure 3: mixins for the Component interface

Julia provides two mixins for the Component interface: The first mixin requires a base class that implements the Controller interface, such as the BasicControllerMixin (this class is an empty implementation of the Controller interface). The second mixin requires a base class that implements the Component interface. These dependencies are summarized in the figure below (an arrow from a mixin A to a mixin B means that A needs a base class that provides the same fields and methods as B - it does not mean that A requires exactly the B mixin).

A.2 Mixins for the TypeFactory interface

Figure 4: mixins for the TypeFactory interface

Julia provides only one mixin for the TypeFactory interface, the BasicTypeFactoryMixin, which provides a basic implementation

A.3 Mixins for the GenericFactory interface

Figure 5: mixins for the GenericFactory interface

Julia provides two mixins for the GenericFactory interface:

A.4 Mixins for the Factory interface

Figure 6: mixins for the Factory interface

Julia provides four mixins for the Factory interface, or more precisely for the Julia Template interface, i.e. for Factory interfaces for Fractal template components:

A.5 Mixins for the BindingController interface

Figure 7: mixins for the BindingController interface

Julia provides a lot of mixins for the BindingController interface. First of all, it provides two mutually exclusive, basic implementations of the BindingController interface: Then four other mixins are provided, which extend any existing BindingController implementation (such as the two previous mixins), in order to perform various checks: Two other mixins are designed to extend the ContainerBindingControllerMixin: And, finally, two other, mutually exclusive mixins are designed to extend the BasicBindingControllerMixin, and target composite components:

A.6 Mixins for the ContentController interface

Figure 8: mixins for the ContentController interface

Julia provides six mixins for the ContentController interface:

A.7 Mixins for the SuperController interface

Figure 9: mixins for the SuperController interface

Julia provides only one mixin for the SuperController interface, which provides a basic implementation of this interface.

A.8 Mixins for the LifeCycleController interface

Figure 10: mixins for the LifeCycleController interface

Julia provides two, mutually exclusive mixins that implement that implement the LifeCycleController interface: These two mixins require methods that are provided by the BasicLifeCycleCoordinator mixin. The TypeLifeCycleMixin extends an existing LifeCycleController implementation in order to check type related constraints before starting a component (more precisely, this mixin checks that all the mandatory client interface of the component and of its direct and indirect sub components are bound). Finally the ContainerLifeCycleMixin extends existing LifeCycleController implementation in order to notify the component's content when it is started or stopped, through its LifeCycleController interface, if it is present (this mixin is intended for "container" components).

A.9 Mixins for the NameController interface

Figure 11: mixins for the NameController interface

Julia provides only one mixin for the NameController interface, which provides a basic implementation of this interface.

B Optimizations

B.1 Intra component optimizations

When object-oriented control membrane are used, it is always possible to merge the controller objects into a single one (which is not the case with component-based ones). The solution is the following:

To be more precise, lets suppose we have four control interfaces I, J, K and L, and three controller classes IImpl, JImpl and KImpl. These classes should look like this:

public class IImpl implements Controller, I {

  // indicates a required interface of type J
  public J weaveableJ;

  // indicates an optional required interface of type L
  public L weaveableOptL;

  // other fields:
  public int foo;

  // implementation of the Controller interface
  public void initFcController (InitializationContext ic) {
    weaveableJ = (J)ic.getInterface("j");
    weaveableOptL = (L)ic.getOptionalInterface("l");
  }

  // other methods:
  public void foo (String name) {
    weaveableJ.bar(weaveableOptL, foo, weaveableC.getFcInterface(name));
  }
}

public class JImpl implements Controller, J {

  public K weaveableK;

  // implementation of the Controller interface ...
  public void initFcController (InitializationContext ic) {
    weaveableK = (K)ic.getInterface("k");
  }
  // other methods (not shown) ...
}

public class KImpl implements Controller, K {
  // other methods ...
}
In the non optimized case, a component with these three controller objects is instantiated in the following steps: In the optimized case, the instantiation process is the following: The "merging" process is the following. Basically, all the methods and fields of each class are copied into a new class (the resulting class does not depend on the order into which the classes are copied). However the fields whose name begins with weaveable are replaced by this, and those whose name begins with weaveableOpt are replaced either by this, if a class that implements the corresponding type is present in the list of the classes to be merged, or null otherwise. Finally, the initFcController methods from the Controller interface are merged into a single initFcController method. The result is the following class:

public class Cb234f2 implements Controller, I, J, K, ... {

  // fields copied from the IImpl class:
  public int foo;
  // fields copied from the JImpl class: none
  // fields copied from the KImpl class: none

  // methods copied from IImpl:
  public void foo (String name) {
    bar(null, foo, getFcInterface(name);
  }
  // methods copied from JImpl (not shown) ...
  // methods copied from KImpl (not shown) ...

  // merged initFcController method
  public void initFcController (InitializationContext ic) {
    (J)ic.getInterface("j");
    (L)ic.getOptionalInterface("l");
    (K)ic.getInterface("k");
  }
}
Notes:

B.2 Inter component optimizations

In addition to the previous intra component optimizations, which are mainly used to save memory, Julia also provides an inter component optimization, namely an algorithm to create and update shortcut links between components, and whose role is to improve time performances.

As explained in section 4.1, each interface of a component contains an "impl" link to an object that really implements the component interface. In the case of a server interface s, this link generally points to an interceptor object, which itself points to the server interface to which the complementary client interface of s is bound:

Figure 12: implementation of bindings with CompositeBindingMixin

More precisely, this is the case with the CompositeBindingMixin (see appendix A.5). With the OptimizedCompositeBindingMixin, the "impl" links are optimized when possible. For example, in the above figure, since I1 does not have an associated interceptor object, and since component interface objects such as I2 just forward any incoming method calls to the object referenced by their "impl" link, the "impl" link of I1 can directly reference the interceptor associated to I2:

Figure 13: implementation of bindings with OptimizedCompositeBindingMixin