|
![]() |
This section explains how to customize the membranes of component through the configuration, composition and creation of controllers and interceptors.
It is possible to customize controllers, by specifying a control interface and an implementation.
Controllers are configured in a simple XML configuration file, which has the following structure :
<componentConfiguration>
<controllers>
<controller>
<interface>ControllerInterface</interface>
<implementation>ControllerImplementation</implementation>
</controller>
...
Unless they some controllers are also interceptors (see later on), the controllers do not have to be ordered.
A default configuration file is provided, it defines the default controllers available for every ProActive component (super, binding, content, naming, lifecycle and component parameters controllers).
A custom configuration file can be specified (in this example with "thePathToMyConfigFile") for
any component in the controller description parameter of the newFcInstance
method from the Fractal API :
componentInstance = componentFactory.newFcInstance(
myComponentType,
new ControllerDescription(
"name",
myHierarchicalType,
thePathToMyControllerConfigFile),
myContentDescription);
The controller interface is a standard interface which defines which methods are available.
When a new implementation is defined for a given controller interface, it has to conform to the following rules :
public ControllerImplementation(Component owner) {
super(owner);
}
protected void setControllerItfType() {
try {
setItfType(ProActiveTypeFactory.instance().createFcItfType(
"Name of the controller",
TypeFactory.SINGLE));
} catch (InstantiationException e) {
throw new ProActiveRuntimeException("cannot create controller type : " +
this.getClass().getName());
}
}
Controllers can also act as interceptors : they can intercept incoming invocations and outgoing invocations. For each invocation, pre and post processings are defined in the methods beforeInputMethodInvocation, afterInputMethodInvocation, beforeOutputMethodInvocation, and afterOutputMethodInvocation. These methods are defined in the interfaces InputInterceptor and OutputInterceptor, and take a MethodCall object as an argument. MethodCall objects are reified representations of method invocations, and they contain Method objects, along with the parameters of the invocation.
Interceptors are configured in the controllers XML configuration file, by simply adding input-interceptor="true" or/and output-interceptor="true" as attributes of the controller element in the definition of a controller (provided of course the specified interceptor is an input or/and output interceptor). For example a controller that would be an input interceptor and an output interceptor would be defined as follows :
<componentConfiguration>
<controllers>
....
<controller input-interceptor="true" output-interceptor="true">
<interface>InterceptorControllerInterface</interface>
<implementation>ControllerImplementation</implementation>
</controller>
...
Interceptors can be composed in a basic manner : sequentially.
For input interceptors, the beforeInputMethodInvocation method is called sequentially for each controller in the order they are defined in the controllers configuration file. The afterInputMethodInvocation method is called sequentially for each controller in the reverse order they are defined in the controllers configuration file.
If in the controller config file, the list of input interceptors is in this order (the order in
the controller config file is from top to bottom) :
InputInterceptor1
InputInterceptor2
This means that an invocation on a server interface will follow this path :
--> caller
--> InputInterceptor1.beforeInputMethodInvocation
--> InputInterceptor2.beforeInputMethodInvocation
--> callee.invocation
--> InputInterceptor2.afterInputMethodInvocation
--> InputInterceptor1.afterInputMethodInvocation
For output interceptors, the beforeOutputMethodInvocation method is called sequentially for
each controller in the order they are defined in the controllers configuration file.
The afterOutputMethodInvocationmethod is called sequentially for each controller in the reverse order they are defined in the
controllers configuration file.
If in the controller config file, the list of input interceptors is in this order (the order in the controller config file is from top to bottom) :
OutputInterceptor1
OutputInterceptor2
This means that an invocation on a server interface will follow this path
--> currentComponent
--> OutputInterceptor1.beforeOutputMethodInvocation
--> OutputInterceptor2.beforeOutputMethodInvocation
--> callee.invocation
--> OutputInterceptor2.afterOutputMethodInvocation
--> OutputInterceptor1.afterOutputMethodInvocation
An interceptor being a controller, it must follow the rules explained above for the creation of a custom controller.
Input interceptors and output interceptors must implement respectively the interfaces InputInterceptor and OutputInterceptor, which declare interception methods (pre/post interception) that have to be implemented.
Here is a simple example of an input interceptor :
public class MyInputInterceptor extends AbstractProActiveController
implements InputInterceptor, MyController {
public MyInputInterceptor(Component owner) {
super(owner);
}
protected void setControllerItfType() {
try {
setItfType(ProActiveTypeFactory.instance().createFcItfType("my controller",
MyController.class.getName(), TypeFactory.SERVER,
TypeFactory.MANDATORY, TypeFactory.SINGLE));
} catch (InstantiationException e) {
throw new ProActiveRuntimeException("cannot create controller " +
this.getClass().getName());
}
}
// foo is defined in the MyController interface
public void foo() {
// foo implementation
}
public void afterInputMethodInvocation(MethodCall methodCall) {
System.out.println("post processing an intercepted an incoming functional invocation");
// interception code
}
public void beforeInputMethodInvocation(MethodCall methodCall) {
System.out.println("pre processing an intercepted an incoming functional invocation");
// interception code
}
}
The configuration file would state :
<componentConfiguration>
<controllers>
....
<controller input-interceptor="true">
<interface>MyController</interface>
<implementation>MyInputInterceptor</implementation>
</controller>
...
In this implementation of the Fractal component model, Fractal components are active objects. Therefore it is possible to redefine their activity. In this context of component based programming, we call an activity redefined by a user a functional activity.
When a component is instantiated, its lifecycle is in the STOPPED state, and the functional activity that a user may have redefined is not started yet. Internally, there is a default activity which handles controller requests in a FIFO order.
When the component is started, its lifecycle goes to the STARTED state, and then the functional activity is started : this activity is initialized (as defined in InitActive), and run (as defined in RunActive).
2 conditions are required for a smooth integration between custom management of functional activities and lifecycle of the component :
Control invocations to stop the component will automatically set the isActive() return value to false, which implies that when the functional activity loops on the body.isActive() condition, it will end when the lifecycle of the component is set to STOPPED.
Communications between components in a hierarchical model may involve the crossing of several membranes, and therefore paying the cost of several indirections. If the invocations are not intercepted in the membranes, then it is possible to optimize the communication path by shortcutting : communicating directly from a caller component to a callee component by avoiding indirections in the membranes.
In the Julia implementation, a shortcut mechanism is provided for components in the same JVM, and the implementation of this mechanism relies on code generation techniques.
We provide a shortcut mechanism for distributed components, and the implementation of this mechanism relies on a "tensioning" technique : the first invocation determines the shortcut path, then the following invocations will use this shortcut path.
For example, in the following pictures, the first invocation from the only client interface of component in node2 follows the standard communication path, as defined during the binding process, and the invocation eventually reaches the server interface of the component in node 3. If no interception takes place during the transfer of the invocation, then a shortcut is possible, and further invocations from the client interface of component in node2 will go directly to the server interface of the component in node 3.
|
![]() |
a configuration without shorctut | a configuration with shorctut between the components in node2 and node3 |
Shortcuts are available when composite components are synchronous components (this does not break the ProActive model, as composite components are structural components). Components can be specified as synchronous in the ControllerDescription object that is passed to the component factory :
ControllerDescription controllerDescription =
new ControllerDescription("name",
Constants.COMPOSITE, Constants.SYNCHRONOUS);
When the system property proactive.components.use_shortcuts is set to true, the component system will automatically establish shortcuts between components whenever possible.