This chapter explains the specific features and functionalities of the GCM Implementation.
The Architecture Description Languages (ADL) are a way to describe software and/or system architectures. ADLs facilitate application description without concern for the underlying implementation code and foster code reuse as an effect of decoupling the implementation from the architecture. Architectures created by using ADLs are composed of predefined entities with various connectors that communicate through defined connections. To define an architecture through an ADL, we can use a textual syntax and/or a graphical syntax, possibly associated with a design tool.
This GCM implementation reuses and extends the Fractal ADL Project. For detailed information on Fractal ADL, read the Fractal ADL tutorial . This mechanism is used to configure and deploy component systems through normalized XML files. Thanks to a specific XML DTD, it specifies a definition for each component of the application. For instance, it usually describes component interfaces, component bindings, component attributes, the subcomponents in the case of a composite component, the virtual node where the component will be deployed, and so on. As it is an extension of the standard Fractal ADL, GCM allows reusing and integrating ProActive-specific features such as distributed deployment using deployment descriptors, active objects, virtual nodes, etc. For example, in the case of virtual nodes the components ADL has to be associated with a deployment descriptor (this is done at parsing time: both files are given to the parser).
Components are defined in
definition
files with the .fractal extension. Here is a simple
example of an ADL
file extract from the example
Helloworld available in the
Examples/org/objectweb/proactive/examples/components/helloworld
directory:
1:<?xml version="1.0" encoding="ISO-8859-1" ?> 2:<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN" "classpath://org/objectweb/proactive/core/component/adl/xml/proactive.dtd"> 3: 4:<definition name="org.objectweb.proactive.examples.components.helloworld.HelloWorld"> 5: <interface name="m" role="server" signature="org.objectweb.proactive.examples.components.helloworld.Main"/> 6: 7: <component name="client" definition="org.objectweb.proactive.examples.components.helloworld.ClientImpl"/> 8: <component name="server"> 9: <interface name="s" role="server" signature="org.objectweb.proactive.examples.components.helloworld.Service"/> 10: <content class="ServerImpl"/> 11: <attributes signature="org.objectweb.proactive.examples.components.helloworld.ServiceAttributes"> 12: <attribute name="header" value="-> "/> 13: <attribute name="count" value="1"/> 14: </attributes> 15: <controller desc="primitive"/> 16: </component> 17: 18: <binding client="this.m" server="client.m"/> 19: <binding client="client.s" server="server.s"/> 20: 21: <controller desc="composite"/> 22: 23: <virtual-node name="helloworld-node"/> 24:</definition> 25:
Now, here is a detailed description of each lines:
1: Classical prologue of XML files.
2: The syntax of the document is validated against a DTD retrieved from the classpath attribute.
4: The definition element has a name (which has to be the same name that the file's without its extension). Inheritance is supported through the 'extends' attribute.
5: The interface element allows to specify interfaces of the current enclosing component.
7-16: Nesting is allowed for composite components and is done by adding other component elements. Components can be specified and created in this definition, and these components can themselves be defined here or in other definition files.
10: Primitive components specify the content element, which indicates the implementation class containing the business logic for this component.
11-14: Components can specify a attributes element, which allows to initialize attributes of a component.
18-19: The binding element specifies bindings between interfaces of components and specifying 'this' as the name of the component refers to the current enclosing component.
21: The controller elements can have the following 'desc' values: 'primitive' or 'composite'.
23: The virtual-node element offers distributed deployment information. It can be exported and composed in the exported VirtualNodes element.
The component will be instantiated on the specified virtual node (or the one that is exported). If there are several nodes mapped on the virtual node, the component will be instantiated on one of the nodes of the virtual node.
The syntax is similar to the standard Fractal ADL and the parsing engine has been extended. Features specific to ProActive are:
Virtual nodes can be exported and composed.
Template components are not handled.
The validating DTD has to be specified as
classpath://org/objectweb/proactive/core/component/adl/xml/proactive.dtd
Components are deployed on the virtual node that is specified in their definition. It has to appear in the deployment descriptor unless this virtual node is exported. In this case, the name of the exported virtual node should appear in the deployment descriptor, unless this exported virtual node is itself exported.
When exported, a virtual node can take part in the composition of other exported virtual nodes. The idea is to further extend reusability of existing (and packaged, packaging being a forthcoming feature of Fractal) components.
This is particularly useful when you want to use a component created by someone else. The programmer who implemented this component may used his own virtual node that you cannot use. Exporting his virtual node, the programmer enables you to redefine this virtual node and thus, you can deploy the component on every virtual node you want. You can also make composition of virtual nodes deterring you from bothering with the definition (in your deployment descriptor) of many virtual nodes. Moreover, a composition of exported virtual nodes also enables to gather two or several components on a same virtual node. This is an essential aspect which provides you with a means to overcome latency bottlenecks when a lot of communications is needed between several components.
In the example, the component defined in helloworld-distributed-wrappers.fractal exports the virtual nodes VN1 and VN2:
<exportedVirtualNodes>
<exportedVirtualNode name="VN1">
<composedFrom>
<composingVirtualNode component="client" name="client-node"/>
</composedFrom>
</exportedVirtualNode>
<exportedVirtualNode name="VN2">
<composedFrom>
<composingVirtualNode component="server" name="server-node"/>
</composedFrom>
</exportedVirtualNode>
</exportedVirtualNodes>
VN1 is composed of the exported virtual node 'client-node' from the component named client
In the definition of the client component (ClientImpl.fractal), we can see that client-node is an exportation of a virtual node which is also name 'client-node':
<exportedVirtualNodes>
<exportedVirtualNode name="client-node">
<composedFrom>
<composingVirtualNode component="this" name="client-node"/>
</composedFrom>
</exportedVirtualNode>
</exportedVirtualNodes>
<virtual-node name="client-node"/>
Although this is a simplistic example, one should foresee a situation where ClientImpl would be a prepackaged component, where its ADL could not be modified. The exportation and composition of virtual nodes allow to adapt the deployment of the system depending on the existing infrastructure. Collocation can be specified in the enclosing component definition (helloworld-distributed-wrappers.fractal):
<exportedVirtualNodes>
<exportedVirtualNode name="VN1">
<composedFrom>
<composingVirtualNode component="client" name="client-node"/>
</composedFrom>
<composedFrom>
<composingVirtualNode component="server" name="server-node"/>
</composedFrom>
</exportedVirtualNode>
</exportedVirtualNodes>
As a result, the client and server component will be colocated / deployed on the same virtual node. This can be profitable if there is a lot of communications between these two components.
When specifying 'null' as the name of an exported virtual node, the components will be deployed on the current virtual machine (helloworld-local-no-wrappers). This can be useful for debugging purposes.
<exportedVirtualNodes>
<exportedVirtualNode name="null">
<composedFrom>
<composingVirtualNode component="client" name="client-node"/>
</composedFrom>
</exportedVirtualNode>
<exportedVirtualNode name="null">
<composedFrom>
<composingVirtualNode component="server" name="server-node"/>
</composedFrom>
</exportedVirtualNode>
</exportedVirtualNodes>
For more information on exported virtual nodes, please refer to [PhD-Morel]
ADL definitions correspond to component factories. ADL definition can be used directly:
Factory factory = org.objectweb.proactive.core.component.adl.FactoryFactory.getFactory();
Map<String, Object> context = new HashMap<String, Object>();
Component c = (Component) factory.newComponent("myADLDefinition", context);
It is also possible to use the launcher tool, which
parses the ADL, creates a corresponding component
factory, instantiates and assembles the components
as defined in the ADL. This launcher is defined in the
org.objectweb.proactive.core.component.adl.Launcher
class and it can be used as follows:
Launcher [-java|-fractal] <definition> [<itf>] [deployment-descriptor])
where [-java|-fractal] comes from the Fractal ADL Launcher (put -fractal for ProActive/GCM components), <definition> is the name of the component to be instantiated and started, <itf> is the name of its Runnable interface if it has one, and <deployment-descriptor> the location of the ProActive deployment descriptor to use. It is also possible to use this class directly from its static main method.
The API is the same as for any Fractal implementation, though some classes are GCM-specific or implementation-specific.
Thus to get the bootstrap component, there are three possibilities:
Use the standard Fractal API with one of
the methods
org.objectweb.fractal.util.Fractal#getBootstrapComponent(...).
In that case, the
fractal.provider system
property has to be set.
Use the standard GCM API with one of the
methods
org.etsi.uri.gcm.util.GCM#getBootstrapComponent(...).
In that case, the
gcm.provider system
property has to be set.
Use the ProActive/GCM API with one of the
methods
org.objectweb.proactive.core.component.Utils#getBootstrapComponent(...).
In that case, the system property to be
set can be either
gcm.provider or
fractal.provider.
This last solution is the one used in all
examples and tests provided through
ProActive.
In all cases, for this implementation the used system
property has to be set to
org.objectweb.proactive.core.component.Fractive.
The
org.objectweb.proactive.core.component.Utils
class also contains several useful methods to
handle components. Moreover, as for any Fractal
or GCM implementation, ProActive/GCM also
supports the use of other methods of the
org.objectweb.fractal.util.Fractal
and
org.etsi.uri.gcm.util.GCM
classes.
As this implementation is based on ProActive, several conditions are required (more in Chapter 2. Active Objects: Creation And Advanced Concepts):
The base class for the implementation of a primitive component has to provide a no-argument and preferably an empty constructor.
Asynchronous method calls with transparent futures is a core feature of ProActive Chapter 2.7. Asynchronous calls and futures) and it allows concurrent processing. Indeed, suppose a caller invokes a method on a callee. This method returns a result on a component. With synchronous method calls, the flow of execution of the caller is blocked until the result of the called method is received. In the case of intensive computations, this can be relatively long. With asynchronous method calls, the caller gets a future object and will continue its tasks until it really uses the result of the method call. The process is then blocked (it is called wait-by-necessity) until the result has effectively been calculated.
Thus, for asynchronous invocations, return types of the methods provided by the interfaces of the components have to be reifiable (Non-final and serializable class) and methods must not throw exceptions.
When a component is instantiated with the
newFcInstance(Type type, Object controllerDesc, Object contentDesc)
method of the
org.objectweb.fractal.api.factory.Factory
class, in addition to the type of the component have to
be specified the controller description and the content
description of the component.
The controller description (
org.objectweb.proactive.core.component.ControllerDescription
) is useful to describe the controllers of components.
It allows to define:
the name of a component.
the hierarchical type of a component.
the custom controllers for a component. The configuration of the controllers is described in a properties file whose location can be given as a parameter. The controllers configuration file is simple: it associates the signature of a controller interface with the implementation that has to be used. During the construction of the component, the membrane is automatically constructed with these controllers. The controllers are linked together and requests targeting a control interface visit the different controllers until they find the suitable one. Then, the request is executed on this controller.
The role of the content description
(
org.objectweb.proactive.core.component.ContentDescription
)
is to define some informations about a component:
the classname of the component (the only one information mandatory).
the constructor parameters of the component (optional).
the activity as defined in the ProActive model (optional). See Chapter 2. Active Objects: Creation And Advanced Concepts for more informations about activity in ProActive.
the meta-object factory for the component (optional).
Collective interactions are a GCM extension to the Fractal model, described in section Section 4.3, “Collective interfaces”, that relies on collective interfaces.
This feature provides collective interactions (1-to-n and n-to-1 interactions between components), namely multicast and gathercast interfaces
By using the
org.etsi.uri.gcm.api.control.MonitorController
controller, users can retrieve various statistics on
components such as the average length of the queue of
a given method or the last execution time of another
method. Thus, with these metrics, users can be
informed on the QoS (Quality of Service) and then
decide to do some changes in their application to
improve its performance.
After having started the monitoring (Method
startGCMMonitoring()), for each
methods exposed by the server interfaces of a
component this controller will be able to provide an
instance of
org.objectweb.proactive.core.component.control.MethodStatistics,
which itself provides some statistics related to the
method.
The set of statistics that can be retrieved and the
corresponding methods to call are described through
the MethodStatistics interface:
/** * Get the current length of the requests incoming queue related to the monitored method. * * @return The current number of pending request in the queue. */ public int getCurrentLengthQueue(); /** * Get the average number of requests incoming queue per second related to the monitored * method since the monitoring has been started. * * @return The average number of requests per second. */ public double getAverageLengthQueue(); /** * Get the average number of requests incoming queue per second related to the monitored * method in the last past X milliseconds. * * @param pastXMilliseconds The last past X milliseconds. * @return The average number of requests per second. */ public double getAverageLengthQueue(long pastXMilliseconds); /** * Get the latest service time for the monitored method. * * @return The latest service time in milliseconds. */ public long getLatestServiceTime(); /** * Get the average service time for the monitored method since the monitoring has been started. * * @return The average service time in milliseconds. */ public double getAverageServiceTime(); /** * Get the average service time for the monitored method during the last N method calls. * * @param lastNRequest The last N method calls. * @return The average service time in milliseconds. */ public double getAverageServiceTime(int lastNRequest); /** * Get the average service time for the monitored method in the last past X milliseconds. * * @param pastXMilliseconds The last past X milliseconds. * @return The average service time in milliseconds. */ public double getAverageServiceTime(long pastXMilliseconds); /** * Get the latest inter-arrival time for the monitored method. * * @return The latest inter-arrival time in milliseconds. */ public long getLatestInterArrivalTime(); /** * Get the average inter-arrival time for the monitored method since the monitoring has * been started. * * @return The average inter-arrival time in milliseconds. */ public double getAverageInterArrivalTime(); /** * Get the average inter-arrival time for the monitored method during the last * N method calls. * * @param lastNRequest The last N method calls. * @return The average inter-arrival time in milliseconds. */ public double getAverageInterArrivalTime(int lastNRequest); /** * Get the average inter-arrival time for the monitored method in the last past X * milliseconds. * * @param pastXMilliseconds The last past X milliseconds. * @return The average inter-arrival time in milliseconds. */ public double getAverageInterArrivalTime(long pastXMilliseconds); /** * Get the average permanence time in the incoming queue for a request of the monitored method * since the monitoring has been started. * * @return The average permanence time in the incoming queue in milliseconds. */ public double getAveragePermanenceTimeInQueue(); /** * Get the average permanence time in the incoming queue for a request of the monitored method * during the last N method calls. * * @param lastNRequest The last N method calls. * @return The average permanence time in the incoming queue in milliseconds. */ public double getAveragePermanenceTimeInQueue(int lastNRequest); /** * Get the average permanence time in the incoming queue for a request of the monitored method * in the last past X milliseconds. * * @param pastXMilliseconds The last past X milliseconds. * @return The average permanence time in the incoming queue in milliseconds. */ public double getAveragePermanenceTimeInQueue(long pastXMilliseconds); /** * Get the list of all the method calls (server interfaces) invoked by a given invocation. * * @return The list of the used interfaces. * TODO which kind of information do you need (Interface reference, name, ...?) */ public List<String> getInvokedMethodList();
In regard to the MethodStatistics
instances, they can be recovered by the following
MonitorController's methods:
public Map<String, Object> getAllGCMStatistics();
Which returns the statistics of each
methods of each server interfaces of
the monitored component through a
Map where values
are objects which can be cast to
MethodStatistics.
The key to obtain the
MethodStatistics
corresponding to a method can be
generated thanks to the method
org.objectweb.proactive.core.component.control.MonitorControllerHelper#generateKey()
which takes as parameters the
interface name, the method name and an
array of parameter types of the method.
public Object getGCMStatistics(String itfName, String methodName, Class<?>[] parametersTypes)
Which returns an object which can be
cast to
MethodStatistics and
corresponds to the statistics of the
method methodName which takes as
parameters the given parameter types
and belongs to the interface itfName.
These methods to retrieve
MethodStatistics are in
"immediate services", i.e. when calling one of
these methods, the corresponding request will
not be enqueued in the component queue as any
other request but it will be executed immediately
and thus avoiding to spend to much time to get
statistics.
In order to define non functional prioritized requests (useful for instance for life cycle management, reconfiguration, ...), a partial order between each kind of request is available to specify when an incoming request can pass requests already in the queue.
Here are the different priorities available for the requests:
F: Functional request. Always goes at the end of the requests queue.
NF1: Standard Non Functional request. Also always goes at the end of the requests queue.
NF2: Non Functional prioritized request. Pass the Functional requests into the requests queue but respect the order of the other Non Functional requests.
NF3: Non Functional most prioritized request. Pass all the other requests into the requests queue.
The following picture represents the addition of non functional requests depending on their priorities:
Thus, for prioritize non functional requests, the
org.etsi.uri.gcm.api.control.PriorityController
controller has to be used:
public interface PriorityController {
/**
* All the possible kinds of priority for a request on the component to which this interface belongs.
*
*/
public enum RequestPriority {
/**
* Functional priority
*/
F,
/**
* Non-Functional priority
*/
NF1,
/**
* Non-Functional priority higher than Functional priority (F)
*/
NF2,
/**
* Non-Functional priority higher than Functional priority (F) and Non-Functional priorities (NF1 and
* NF2)
*/
NF3;
}
/**
* Set priority of a method exposed by a server interface of the component to which this interface belongs.
*
* @param itfName Name of an interface of the component to which this interface belongs.
* @param methodName Name of a method exposed by the interface corresponding to the given interface name.
* @param parameterTypes Parameter types of the method corresponding to the given method name.
* @param priority Priority to set to the method corresponding to the given method name.
* @throws NoSuchInterfaceException If there is no such server interface.
* @throws NoSuchMethodException If there is no such method.
*/
public void setGCMPriority(String itfName, String methodName, Class<?>[] parameterTypes,
RequestPriority priority) throws NoSuchInterfaceException, NoSuchMethodException;
/**
* Get the priority of a method exposed by a server interface of the component to which this interface
* belongs.
*
* @param itfName Name of an interface of the component to which this interface belongs.
* @param methodName Name of a method exposed by the interface corresponding to the given interface name.
* @param parameterTypes Parameter types of the method corresponding to the given method name.
* @return Priority of this method.
* @throws NoSuchInterfaceException If there is no such server interface.
* @throws NoSuchMethodException If there is no such method.
*/
public RequestPriority getGCMPriority(String itfName, String methodName, Class<?>[] parameterTypes)
throws NoSuchInterfaceException, NoSuchMethodException;
}
Stream ports allow to ensure to have component interfaces which only have one way communication methods (client side to server side).
Thus, by using the
org.objectweb.proactive.core.component.type.StreamInterface
interface as a tag on the java interface definition of a
component interface, the GCM implementation will check
during the instantiation of an Interface Type created with
the createFcItfType() or
createGCMItfType() methods, if all the
methods of the given interface, and its parents, return void.
If not, an
org.objectweb.fractal.api.factory.InstantiationException
exception is thrown accordingly to the Fractal specifications.
In this chapter, we consider multiway communications - communications to or from several interfaces - and notably parallel communications, which are common in Grid computing.
The objective is to simplify the design of distributed Grid applications with multiway interactions.
The driving idea is to manage the semantics and behavior of collective communications at the interface level.
Grid computing uses the resources of many separate computers connected by a network (usually the Internet) to solve large-scale computation problems. Because of the number of available computers, it is fundamental to provide tools for facilitating communications to and from these computers. Moreover, Grids may contain clusters of computers, where local parallel computations can be very efficiently performed (this is part of the solution for solving large-scale computation problems) which means that programming models for Grid computing should include parallel programming facilities. We address this issue, in the context of a component model for Grid computing, by introducing collective interfaces.
The component model that we use (Fractal) proposes two kinds of cardinalities for interfaces, singleton or collection, which result in one-to-one bindings between client and server interfaces. It is possible though to introduce binding components, which act as brokers and may handle different communication paradigms. Using these intermediate binding components, it is therefore possible to achieve one-to-n, n-to-one or n-to-n communications between components. It is not possible however for an interface to express a collective behavior: explicit binding components are needed in this case.
The GCM proposes the addition of new cardinalities in the specification of Fractal interfaces, namely multicast and gathercast. Multicast and gathercast interfaces give the possibility to manage a group of interfaces as a single entity (which is not the case with a collection interface, where the user can only manipulate individual members of the collection), and they expose the collective nature of a given interface. Moreover, specific semantics for multi-way invocations can be configured, providing users with flexible communications to or from gathercast and multicast interfaces. Lastly, avoiding the use of explicit intermediate binding components simplifies the programming model and type compatibility is automatically verified.
The role and use of multicast and gathercast interfaces are complementary. Multicast interfaces are used for parallel invocations whereas gathercast interfaces are used for synchronization and gathering purposes.
![]() |
Note |
|---|---|
|
In this implementation of collective interfaces, new features of the Java language introduced in Java 5 are extensively used, notably annotations and generics. |
A multicast interface transforms a single invocation into a list of invocations
A multicast interface is an abstraction for 1-to-n communications. When a single invocation is transformed into a set of invocations, these invocations are forwarded to a set of connected server interfaces. A multicast interface is unique and it exists at runtime (it is not lazily created). The semantics of the propagation of the invocation, of the distribution of the invocation parameters and of the result (if there is) are customizable (through annotations).
Invocations forwarded to the connected server interfaces occur in parallel, which is one of the main reasons for defining this kind of interface: it enables parallel invocations, with automatic distribution of invocation parameters .
A multicast invocation leads to the invocation services offered by one or several connected server interfaces, with possibly distinct parameters for each server interface.
If some of the parameters of a given method of a multicast interface are lists of values, these values can be distributed in various ways through method invocations to the server interfaces connected to the multicast interface. The default behavior - namely broadcast - is to send the same parameters to each of the connected server interfaces. In the case where some parameters are lists of values, copies of the lists are sent to each receiver. However, similar to what SPMD programming offers, it may be adequate to strip some of the parameters so that the bound components will work on different data. In MPI for instance, this can be explicitly specified by stripping a data buffer and using the scatter primitive.
The following figure illustrates such distribution mechanisms: broadcast (a.) and scatter (b.)
Invocations occur in parallel and the distribution of parameters is automatic.
Five modes of parameter distribution are provided by default, defining distribution policies for a list of parameters:
BROADCAST - copies a list of parameters and sends a copy to each connected server interface.
ParamDispatchMode.BROADCAST
ONE-TO-ONE - sends the ith parameter to the connected server interface of index i. This implies that the number of elements in the annotated list is equal to the number of connected server interfaces.
ParamDispatchMode.ONE_TO_ONE
ROUND-ROBIN - distributes each element of the list parameter in a round-robin fashion to the connected server interfaces.
ParamDispatchMode.ROUND_ROBIN
RANDOM - distributes each element of the list parameter in a random manner to the connected server interfaces.
ParamDispatchMode.RANDOM
UNICAST - sends only one parameter of the list parameters to one of the connected server interfaces.
ParamDispatchMode.UNICAST
By default, the behavior is not specified: there
is no way to predict which parameter will be sent to
which server interface. Therefore, it is strongly
recommended to combine the use of the UNICAST parameter
distribution mode with the dispatch annotation,
org.objectweb.proactive.core.group.Dispatch
, which allows to specify a custom dispatch mode
(This custom dispatch mode has to implement the
org.objectweb.proactive.core.group.DispatchBehavior
interface):
@DispatchMode(mode = DispatchMode.CUSTOM, customMode=CustomUnicastDispatch.class)
It is also possible to define a custom
partition by specifying the partition
algorithm in a class which implements the
org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatch
interface.
@ParamDispatchMetadata(mode=ParamDispatchMode.CUSTOM, customMode=CustomParametersDispatch.class))
![]() |
Note |
|---|---|
|
This implementation of collective interfaces extensively uses new features of the Java language introduced in Java 5, such as generics and annotations. |
The distribution of parameters in this framework is specified in the definition of the multicast interface, using annotations.
Elements of a multicast interface which can be annotated are: interface, methods and parameters. The different distribution modes are explained in the next section. The examples in this section all specify broadcast as the distribution mode.
A distribution mode declared at the level of the interface defines the distribution mode for all parameters of all methods of this interface, but may be overridden by a distribution mode declared at the level of a method or of a parameter.
The annotation for declaring distribution
policies at level of an interface is
@org.objectweb.proactive.core.component.type.annotations.multicast.ClassDispatchMetadata
and is used as follows:
import java.util.List;
import org.objectweb.proactive.examples.documentation.classes.T;
import org.objectweb.proactive.core.component.type.annotations.multicast.ClassDispatchMetadata;
import org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatchMetadata;
import org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatchMode;
@ClassDispatchMetadata(mode = @ParamDispatchMetadata(mode = ParamDispatchMode.BROADCAST))
interface MyMulticastItf {
public void foo(List<T> parameters);
}
A distribution mode declared at the level of a method defines the distribution mode for all parameters of this method, but may be overridden at the level of each individual parameter.
The annotation for declaring distribution
policies at level of a method is
@org.objectweb.proactive.core.component.type.annotations.multicast.MethodDispatchMetadata
and is used as follows:
import java.util.List;
import org.objectweb.proactive.examples.documentation.classes.T;
import org.objectweb.proactive.core.component.type.annotations.multicast.MethodDispatchMetadata;
import org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatchMetadata;
import org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatchMode;
interface MyMulticastItf {
@MethodDispatchMetadata(mode = @ParamDispatchMetadata(mode = ParamDispatchMode.BROADCAST))
public void foo(List<T> parameters);
}
Moreover, an another feature, inherited from the group framework of ProActive, is available: the dynamic dispatch.
The
org.objectweb.proactive.core.group.DispatchMode.DYNAMIC
mode is applicable when there are more parameter
partitions than binded server interfaces. A
partitions is a set of arguments (list elements)
that will be sent to server interfaces. For
instance, instead of giving arguments one-by-one,
you can use a 5-size partition which will send
the first 5 arguments to the first server
interface, the 5 next to the second interface and
so on. Dynamic dispatch uses a knowledge-based
policy, i.e. it collects information about request
execution by server interfaces, and maintains a
ranking among server interfaces so that partitions
are dispatched to the "best" server interface. A
buffer can be configured, in which case buffered
partitions are statically allocated to server
interfaces, according to the static dispatch policy.
Dynamic dispatch must be used like this:
@Dispatch(mode = DispatchMode.DYNAMIC, bufferSize = 1024) public void foo(List<T> parameters);
The annotation for declaring distribution
policies at level of a parameter is
@org.objectweb.proactive.core.component.type.annotations.multicast.ParamDispatchMetadata
and is used as follows:
public void bar(@ParamDispatchMetadata(mode = ParamDispatchMode.BROADCAST) List<T> parameters);
The previous examples have been given with methods which do not return a result since we were focused on parameters. However, it is obviously possible to use multicast with server interfaces returning non-void results. This section aims at giving explanation on how to handle results.
For each invoked method which returns a result
of type
T,
a multicast invocation returns an aggregation
of the results: a
List<T>.
There is a type conversion, from return type
T
in a method of the server interface, to return
type
List<T>
in the corresponding method of the multicast
interface. The framework transparently handles
the type conversion between return types, which
is just an aggregation of elements of type
T
into a structure of type
list<T>.
This implies that, for the multicast interface,
the signature of the invoked method has to
explicitly specify
List<T>
as a return type. This also implies that each
method of the interface returns either nothing,
or a list. Valid return types for methods of
multicast interfaces are illustrated as follows:
public List<T> foo(); public void bar();
Otherwise, there is also a possibility to customize the result values by processing a reduction on them. This mechanism allows to gather results and/or perform some operations on them.
There is one reduction mechanism provided by default: SELECT_UNIQUE_VALUE. It allows to extract of the list of results the only one result that the list contains. For example, this is useful when your multicast mode is UNICAST, so you know that there will be only one element in your returned list.
In order to use it, the multicast interface must
use the
@org.objectweb.proactive.core.component.type.annotations.multicast.Reduce
annotation at the level of the methods which the
results need to be reduced:
import java.util.List;
import org.objectweb.proactive.core.component.type.annotations.multicast.Reduce;
import org.objectweb.proactive.core.component.type.annotations.multicast.ReduceMode;
import org.objectweb.proactive.examples.documentation.classes.T;
public interface MyMulticastItf2 {
@Reduce(reductionMode = ReduceMode.SELECT_UNIQUE_VALUE)
public T baz();
}
Or else, a custom reduce mode can also be used.
For this case, the first step is to define the
reduction algorithm into a class which
implements the
org.objectweb.proactive.core.component.type.annotations.multicast.ReduceBehavior
interface. Then, the multicast interface can use
the Reduce annotation, always at the level of
the methods, by specifying the mode (CUSTOM) and
the implementation class of the reduction to
use:
@Reduce(reductionMode = ReduceMode.CUSTOM, customReductionMode = GetLastReduction.class) public T foobar();
This method will use the
GetLastReduction algorithm which
returns the last element of the list.
Here is the the implementation of the
GetLastReduction class:
package functionalTests.component.collectiveitf.reduction.composite;
import java.io.Serializable;
import java.util.List;
import org.objectweb.proactive.core.component.exceptions.ReductionException;
import org.objectweb.proactive.core.component.type.annotations.multicast.ReduceBehavior;
public class GetLastReduction implements ReduceBehavior, Serializable {
/**
*
*/
private static final long serialVersionUID = 51L;
public Object reduce(List<?> values) throws ReductionException {
System.out.println("--------------");
System.out.println("Getting last out of " + values.size() + " elements");
System.out.println("--------------");
return values.get(values.size() - 1);
}
}
Multicast interfaces manipulate lists of parameters
(sayList<ParamType>) and
expect lists of results (say
List<ResultType>).
With respect to a multicast interface, connected
server interfaces, on the contrary, may work with
lists of parameters (
List<ParamType>), but also
with individual parameters (
ParamType) and return individual
results (ResultType).
Therefore,
the signatures of methods differ from a
multicast client interface to its connected
server interfaces
. This is illustrated in the following
figure: in picture
a, the
foo method of the multicast
interface returns a list of elements of type
T collected from the
invocations to the server interfaces and in the
picture b, the
bar method distributes elements
of type A to the connected
server interfaces.
![]() |
Figure 4.4. Comparison of signatures of methods between client multicast interfaces and server interfaces
For a given multicast interface, the type of server interfaces which may be connected to it can be inferred by applying the following rules:
For a given multicast interface,
the server interface must have the same number of methods
for a given method method foo of the multicast interface, there must be a matching method in the server interface:
named foo
which returns:
void if the method in the multicast method returns void
T if the multicast method returns list<T>
for a given parameter List<T> in the multicast method, there must be a corresponding parameter, either List<T> or T, in the server interface, which matches the distribution mode for this parameter.
The compatibility of interface signatures is
verified automatically at binding time, resulting in
a documented
IllegalBindingException if
signatures are incompatible.
A gathercast interface transforms a list of invocations into a single invocation
A gathercast interface is an abstraction for n-to-1 communications. It handles data aggregation for invocation parameters, as well as process coordination. It gathers incoming data, and can also coordinate incoming invocations before continuing the invocation flow, by defining synchronization barriers.
Gathering operations require knowledge of the participants on the collective communication (i.e. the clients of the gathercast interface). Therefore, the binding mechanism, when performing a binding to a gathercast interface, provides references on client interfaces bound to the gathercast interface. This is handled transparently by the framework. As a consequence, bindings to gathercast interfaces are bidirectional links.
Gathercast interfaces aggregate parameters from method invocations from client interfaces into lists of invocations parameters, and they redistribute results to each client interface.
Invocation parameters are simply gathered into lists of parameters. The indexes of the parameters in the list correspond to the index of the parameters in the list of connected client interfaces, managed internally by the gathercast interface.
The result of the invocation transformed by the gathercast interface is a list of values. Each result value is therefore indexed and redistributed to the client interface with the same index in the list of client interfaces managed internally by the gathercast interface.
Similarly to the distribution of invocation parameters in multicast interfaces, a redistribution function could be applied to the results of a gathercast invocation, however this feature is not implemented yet.
Gathercast interfaces manipulate lists of parameters
(say List<ParamType>)
and return lists of results (say
List<ResultType>).
With respect to a gathercast interface, connected
client interfaces work with parameters which can be
contained in the lists of parameters of the methods
of the bound gathercast interface (ParamType), and
they return results which can be contained in the
lists of results of the methods of the bound
gathercast interface (ResultType).
Therefore, by analogy to the case of multicast
interfaces,
the signatures of methods differ from a
gathercast server interface to its connected
client interfaces
.
This is illustrated in the following figure: the
foo methods of interfaces which are client of the
gathercast interface exhibit a parameter of type
V,
the foo method of the gathercast interface
exhibits a parameter of type
List<V>.
Similarly, the foo method of client interfaces
return a parameter of type T
whereas the foo method of the gathercast
interface returns a parameter of type
List<T>.
The compatibility of interface signatures is
verified automatically at binding time, resulting in
a documented IllegalBindingException
if signatures are incompatible.
An invocation from a client interface to a gathercast interface is asynchronous, which matches with the usual conditions for asynchronous invocations in ProActive. However the gathercast interface only creates and executes a new invocation with gathered parameters when all connected client interfaces have performed an invocation on it.
It is possible to specify a timeout, which corresponds to the maximum amount of time between the moment when the first invocation of a client interface is processed by the gathercast interface, and those when the invocation of the last client interface is processed. Indeed, the gathercast interface will not forward a transformed invocation until all invocations of all client interfaces are processed by this gathercast interface.
Timeouts for gathercast invocations are specified by an annotation on the method subject to the timeout, the value of the timeout is specified in milliseconds:
@org.objectweb.proactive.core.component.type.annotations.gathercast.MethodSynchro(timeout=20)
If a timeout is reached before a gathercast
interface could gather and process all incoming
requests, a
org.objectweb.proactive.core.component.exceptions.GathercastTimeoutException
is returned to each client participating in the
invocation. This exception is a
runtime exception.
It is also possible for gathercast interface not to wait for all invocations from connected client interfaces to perform an invocation by specifying the waitForAll attribute. Therefore, the gathercast interface will create and execute a new invocation on the first invocation received from any of the connected client interfaces.
Thus, this specific feature can be used by the same annotation as for the timeout but with a different attribute:
@org.objectweb.proactive.core.component.type.annotations.gathercast.MethodSynchro(waitForAll=false)
Therefore, the waitForAll attribute accepts boolean values and has for default value "true" (same behavior as if the annotation is not specified).
Furthermore, it is forbidden to combine timeout and
waitForAll set to false (an
org.objectweb.fractal.api.factory.InstantiationException
would be raised) because it would be incoherent.
To create a distributed component system, a deployment framework is available: the GCM Deployment.
Distribution is achieved in a transparent manner over the Java RMI protocol thanks to the use of a stub/proxy pattern. Components are manipulated indifferently of their location (local or on a remote JVM). A complete description of the GCM Deployment can be found at Chapter 14. ProActive Grid Component Model Deployment.
In brief, this framework:
connects to remote hosts using supported protocols, such as rsh, ssh, lsf, oar, etc...
creates JVMs on these hosts
instantiates components on these newly created JVMs
The first step to distribute components is to initiate the deployment by loading the GCM Application Descriptor:
GCMApplication gcma = PAGCMDeployment.loadApplicationDescriptor(filePath);
Then, the deployment must be started (i.e. creation of the remote JVMs):
gcma.startDeployment();
The next step, distribute components, may be done through the ADL or the API.
Distribute components with ADL is quite simple:
Put the GCMApplication into a java.util.Map with as key "deployment-descriptor":
Map<String, Object> context = new HashMap<String, Object>();
context.put("deployment-descriptor", gcma);
Call the usual method to instantiate
component through ADL (method
org.objectweb.fractal.adl.Factory#newComponent())
with the Map containing the GCM Application
as parameter:
Component component = (Component) factory.newComponent("my.adl.folder.ComponentDefinition", context);
Thus, the component will be instantiated in a node of the virtual node specified in the ADL component definition (if a virtual node of the same name has also been defined in the GCM Application Descriptor).
To distribute components with the API, the first thing to do is to get a node from a virtual node defined by the GCM Application Descriptor:
Map<String, GCMVirtualNode> vns = gcma.getVirtualNodes();
vns.get("VN1").waitReady();
Node node = vns.get("VN1").getANode();
Then, the component must be instanced thanks to one
of the methods provided by
org.objectweb.proactive.core.component.factory.PAGenericFactory,
taking as parameter the node obtained previously:
Component component = gf.newFcInstance(componentType, controllerDescription, contentDescription, node);
The distribution of components may also be made by using the ProActive deployment framework. This deployment framework uses the concept of virtual nodes too but it just needs a single configuration file. Thus it may be used in a similar way. More informations are available at Chapter 15. XML Deployment Descriptors.
It is also possible to deploy components with ADL without using a deployment (GCM or ProActive). To do this, the virtual node has to be directly set in the context using as key the virtual node name:
context.put(vn.getName(), vn);
Of course, as many virtual nodes as needed can be set in the context. Then the component can be instantiated as usual:
Component component = (Component) factory.newComponent("my.adl.folder.ComponentDefinition", context);Thus, the component will be instantiated in a node of the virtual node specified in the ADL component definition (if a virtual node of the same name has also been set in the context).
There is a last way to deploy components with ADL and
without using deployment (GCM or ProActive) nor virtual
node, using a list of nodes:
java.util.List<Node>.
This way is mostly useful for tests purpose
since there is no control on which node each component
will be instantiated.
To deploy components using a list of nodes, the first
step is to set this list in the context by setting the
"nodes" key (or by using the corresponding constant
org.objectweb.proactive.core.component.adl.nodes.ADLNodeProvider.NODES_ID):
context.put("nodes", nodes);Then the component can be instantiated as usual:
Component component = (Component) factory.newComponent("my.adl.folder.ComponentDefinition", context);Therefore, the component will be instantiated on a node picked from the list. If the component is a composite component and has several subcomponents, each of them will be instantiated on different nodes. The node used is chosen randomly and there is no way to control which node is used to deploy a specific component. The only guarantee is that no node will be used twice until every node has been used at least once.
If no virtual node has been specified in the ADL component definition, the component is created in the local JVM.
If a virtual node is defined in the ADL component definition and neither a deployment (GCM or ProActive) nor the virtual node has been set in the context (or there is no corresponding virtual node name), then the component is also created in the local JVM.
![]() |
Note |
|---|---|
|
These two points do not apply if a list of nodes is set in the context. |
This section explains how to customize the component membranes 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:
<?xml version="1.0" encoding="UTF-8"?> <componentConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../Core/org/objectweb/proactive/core/component/config/component-config.xsd" name="MyConfigurationName"> <controllers> <controller> <interface> ControllerInterface </interface> <implementation> ControllerImplementation </implementation> </controller> <!-- other controllers --> </controllers> </componentConfiguration>
Unless these 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, multicast, gathercast and monitor controllers, the priority controller is not available by default).
A custom configuration file can be specified (in this example with "thePathToMyConfigFile") for any component in the controller tag of the ADL definition:
<definition name="name">
...
<controller desc="thePathToMyControllerConfigFile"/>
</definition>
Or in the controller description parameter of the newFcInstance method from the Fractal/GCM 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:
The controller implementation has to extend
the AbstractPAController
class, which is the base class for component
controllers in ProActive, and which defines the
constructor
AbstractPAController(Component owner).
The controller implementation must override this constructor:
public ControllerImplementation(Component owner) {
super(owner);
}
The controller implementation must also
override the abstract method
setControllerItfType(),
which sets the type of the controller
interface:
protected void setControllerItfType() {
try {
setItfType(PAGCMTypeFactoryImpl.instance().createFcItfType("Name of the controller",
ControllerItf.class.getName(), TypeFactory.SERVER, TypeFactory.MANDATORY,
TypeFactory.SINGLE));
} catch (InstantiationException e) {
throw new ProActiveRuntimeException("cannot create controller type: " + this.getClass().getName());
}
}
The controller interface and its implementation have to be declared in the component configuration file.
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 as well as 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 (specify of course if the 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:
<controller input-interceptor="true" output-interceptor="true"> <interface> InterceptorControllerInterface </interface> <implementation> ControllerImplementation </implementation> </controller>
Interceptors can be composed in a sequentially manner.
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 controller configuration
file.
In the controller configuration file, if the list of input interceptors is in this order (the order in the controller configuration 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 afterOutputMethodInvocationmethodM
is called sequentially for each controller in the reverse
order they are defined in the controller configuration file.
In the controller configuration file, if the list of input interceptors is in this order (the order in the controller configuration 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 has to follow the rules explained above for the creation of a custom controller.
Input interceptors and output interceptors has to
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:
package org.objectweb.proactive.examples.documentation.components;
import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.factory.InstantiationException;
import org.objectweb.fractal.api.type.TypeFactory;
import org.objectweb.proactive.core.ProActiveRuntimeException;
import org.objectweb.proactive.core.component.control.AbstractPAController;
import org.objectweb.proactive.core.component.interception.InputInterceptor;
import org.objectweb.proactive.core.component.type.PAGCMTypeFactoryImpl;
import org.objectweb.proactive.core.mop.MethodCall;
public class MyInputInterceptor extends AbstractPAController implements InputInterceptor, ControllerItf {
/**
*
*/
private static final long serialVersionUID = 51L;
public MyInputInterceptor(Component owner) {
super(owner);
}
protected void setControllerItfType() {
try {
setItfType(PAGCMTypeFactoryImpl.instance().createFcItfType("mycontroller",
ControllerItf.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:
<controller input-interceptor="true"> <interface> org.objectweb.proactive.examples.documentation.components.ControllerItf </interface> <implementation> org.objectweb.proactive.examples.documentation.components.MyInputInterceptor </implementation> </controller>
This feature allows to expose interfaces of a component as a web service and thus, to call them from any client written in any foreign language.
Indeed, applications written in C#, for instance, cannot communicate with ProActive applications. We have chosen web services technology to enable interoperability because they are based on XML and HTTP. Thus, any component interface can be accessible from any enabled web services language.
A web service is a software entity, providing one or several functionalities, that can be exposed, discovered and accessed over the network. Moreover, web services technology allows heterogeneous applications to communicate and exchange data in a remotely way. In our case, the useful elements, of web services are:
The SOAP Message
The SOAP message is used to exchange XML based data over the internet. It can be sent via HTTP and provides a serialization format for communicating over a network.
The HTTP Server
HTTP is the standard web protocol generally used over the 80 port. In order to receive SOAP messages, you can either install an HTTP server that will be responsible of the data transfer or use the default HTTP server which is a Jetty server. However, This server is not sufficient to treat a SOAP request. For this, you also need a SOAP engine.
The SOAP Engine
A SOAP Engine is the mechanism responsible of making transparent the unmarshalling of the request and the marshalling of the response. Thus, the service developer doesn't have to worry with SOAP. In our case, we can use Axis2 (used by default) or CXF which can be installed into any HTTP server.
The client
Client's role is to consume a web service. It is the producer of the SOAP message. The client developer doesn't have to worry about how the service is implemented. The Axis2 and CXF java libraries provide classes to easily invoke a service (see examples).
If you want to expose your components on the local
embedded Jetty server or on Jetty servers deployed
during your deployment, you just have to deploy
ProActive using the build deploy
command into the compile directory.
If you want to expose them on an other http server, you
have to build the proactive.war
archive. For this, you have to go into your
compile directory of your ProActive
home and type
build proActiveWar[Axis2|CXF]. The
proactive.war file will be built into
the dist directory.
If you have chosen to use the default Jetty server, then you have nothing else to do. Jetty server will automatically take files it needs.
If you have chosen to use your own HTTP server, then you
just have to copy the proactive.war file into the
webapp directory of your HTTP server.
Some HTTP servers need to be restarted to take into account
this new web application but some others like Tomcat can
handle hot deployment and, thus, do not need to be restarted.
![]() |
Warning |
|---|---|
|
If you use your own HTTP server, you have to be aware that the Jetty server will be launched. Most of ProActive examples are launched with the option '-Dproactive.http.port=8080' which specifies Jetty port. So, if you want to use your own server on port 8080, you have to modify the jetty port in launching scripts or to change the port of your own server. |
There are two ways of exposing and unexposing a component as a
web service: the first one is the same as we can do with
active objects and the second one uses the
PAWebServicesController, which is a
component controller dedicated to the web services exposition.
This section deals with the first method.
The steps for exporting and using a component as a web service are the following:
Create, instantiate and start your component in a classic way as below:
Component boot = Utils.getBootstrapComponent();
GCMTypeFactory tf = GCM.getGCMTypeFactory(boot);
GenericFactory cf = GCM.getGenericFactory(boot);
// type of server component
ComponentType sType = tf.createFcType(new InterfaceType[] { tf.createFcItfType("hello-world", A.class
.getName(), false, false, false) });
// create server component
Component a = cf.newFcInstance(sType, new ControllerDescription("server", Constants.PRIMITIVE),
new ContentDescription(AImpl.class.getName()));
//start the component
GCM.getGCMLifeCycleController(a).startFc();
Once the element created and activated, a WebServicesFactory object has to be instantiated:
WebServicesFactory wsf;
wsf = AbstractWebServicesFactory.getWebServicesFactory("axis2");
or
WebServicesFactory wsf;
wsf = AbstractWebServicesFactory.getWebServicesFactory("cxf");
or else
WebServicesFactory wsf; wsf = AbstractWebServicesFactory.getDefaultWebServicesFactory();
Then, you have to instantiate a WebServices object as follows:
WebServices ws = wsf.getWebServices(myUrl);
If you want to use the local Jetty server to expose your component, you can use the following line to retrieve the good URL:
String myUrl = AbstractWebServicesFactory.getLocalUrl();
This will get the Jetty port which is a random port and build the url.
And finally, you can used one of the
WebServices methods:
/**
* Expose a component as a web service. Each server interface of the component
* will be accessible by the urn [componentName]_[interfaceName].
* Only the interfaces public methods of the specified interfaces in
* <code>interfaceNames</code> will be exposed.
*
* @param component The component owning the interfaces that will be deployed as web services.
* @param componentName Name of the component
* @param interfaceNames Names of the interfaces we want to deploy.
* If null, then all the interfaces will be deployed
* @throws WebServicesException
*/
public void exposeComponentAsWebService(Component component, String componentName, String[] interfaceNames)
throws WebServicesException;
/**
* Expose a component as web service. Each server interface of the component
* will be accessible by the urn [componentName]_[interfaceName].
* All the interfaces public methods of all interfaces will be exposed.
*
* @param component The component owning the interfaces that will be deployed as web services.
* @param componentName Name of the component
* @throws WebServicesException
*/
public void exposeComponentAsWebService(Component component, String componentName)
throws WebServicesException;
/**
* Undeploy all the client interfaces of a component deployed on a web server.
*
* @param component The component owning the services interfaces
* @param componentName The name of the component
* @throws WebServicesException
*/
public void unExposeComponentAsWebService(Component component, String componentName)
throws WebServicesException;
/**
* Undeploy the given client interfaces of a component deployed on a web server.
* If the array of interface names is null, then undeploy all the interfaces of
* the component.
*
* @param component The component owning the services interfaces
* @param componentName The name of the component
* @param interfaceNames Interfaces tp be undeployed
* @throws WebServicesException
*/
public void unExposeComponentAsWebService(Component component, String componentName,
String[] interfaceNames) throws WebServicesException;
/**
* Undeploy specified interfaces of a component deployed on a web server
*
* @param componentName The name of the component
* @param interfaceNames Interfaces to be undeployed
* @throws WebServicesException
*/
public void unExposeComponentAsWebService(String componentName, String[] interfaceNames)
throws WebServicesException;
where:
component is the component
componentName the first part of the service name which will identify the component on the server. The service will be exposed at the address http://[host]:[port]/proactive/services/componentName_interfaceName
interfaceNames is a String array containing the interface names of the component you want to make accessible. If this parameters is null (or is absent), all the functional server interfaces of the component will be exposed.
As said in the previous section, it is possible to expose web services in two differents manners. This section explains how to proceed with the dedicated web services controller.
First, instantiate your component with the web service controller:
// Get the web services controller corresponding to the chosen framework (here axis2)
String controllersConfigFileLocation = AbstractPAWebServicesControllerImpl.getControllerFileUrl(
"axis2").getPath();
// Create the component using this controller
ControllerDescription cd = new ControllerDescription("server", Constants.PRIMITIVE,
controllersConfigFileLocation);
Component myComp = genericFactory.newFcInstance(componentType, cd, new ContentDescription(
HelloWorldComponent.class.getName()));
The first line is meant to retrieve the path of the
controller file. This file is located at
"<your ProActive Home>/src/Extensions/org/objectweb/proactive/extensions/webservices/axis2/initialization/axis2-component-config.xml"
for the Axis2 controller or at
"<your ProActive Home>/src/Extensions/org/objectweb/proactive/extensions/webservices/cxf/initialization/cxf-component-config.xml"
for the CXF controller.
Then, get the controller to be able to use it:
// Get the web services controller
PAWebServicesController wsc = org.objectweb.proactive.extensions.webservices.component.Utils
.getPAWebServicesController(myComp);
Then, you have to set the url where you want the controller exposes the service:
// Set the Url where the component has to be exposed
wsc.setUrl("http://localhost:8080/");
If you want to use the local Jetty server to expose your component, you can use the following line to retrieve the good URL:
String url = wsc.getLocalUrl();
This will get the Jetty port which is a random port and build the url.
And finally, you can used one of the
PAWebServicesController methods:
/**
* Expose a component as a web service. Each server interface of the component
* will be accessible by the urn [componentName]_[interfaceName].
* Only the interfaces public methods of the specified interfaces in
* <code>interfaceNames</code> will be exposed.
*
* @param componentName Name of the component
* @param interfaceNames Names of the interfaces we want to deploy.
* If null, then all the interfaces will be deployed
* @throws WebServicesException
*/
public void exposeComponentAsWebService(String componentName, String[] interfaceNames)
throws WebServicesException;
/**
* Expose a component as web service. Each server interface of the component
* will be accessible by the urn [componentName]_[interfaceName].
* All the interfaces public methods of all interfaces will be exposed.
*
* @param componentName Name of the component
* @throws WebServicesException
*/
public void exposeComponentAsWebService(String componentName) throws WebServicesException;
/**
* Undeploy all the client interfaces of a component deployed on a web server.
*
* @param componentName The name of the component
* @throws WebServicesException
*/
public void unExposeComponentAsWebService(String componentName) throws WebServicesException;
/**
* Undeploy specified interfaces of a component deployed on a web server
*
* @param componentName The name of the component
* @param interfaceNames Interfaces to be undeployed
* @throws WebServicesException
*/
public void unExposeComponentAsWebService(String componentName, String[] interfaceNames)
throws WebServicesException;
where:
componentName the first part of the service name which will identify the component on the server. The service will be exposed at the address http://[host]:[port]/proactive/services/componentName_interfaceName
interfaceNames is a String array containing the interface names of the component you want to make accessible. If this parameters is null (or is absent), all the functional server interfaces of the component will be exposed.
When you call the exposition of your component, the Axis2 servlet or the CXF one is deployed on the local Jetty server. It is therefore possible to expose your object locally without doing anything else.
Now, let us assume that your component is deployed on a remote node. In that case, you have a running jetty server on the remote host containing this node but no servlet has been deployed on it. So, if you want to expose your component on the remote host, you will have to make a small modification of the previous piece of code.
The web service extension provides classes that implements the
InitActive interface which are in charge of
deploying the CXF or the Axis2 servlet on the host where your
object has been initialized. Thus, you just have to instantiate
your component using this implementation of
InitActive.
// Get the Axis2 InitActive in charge of deploying the Axis2 Servlet
InitActive axis2InitActive = WebServicesInitActiveFactory.getInitActive("axis2");
Component myComponent = ((PAGenericFactory) genericFactory).newFcInstance(componentType,
new ControllerDescription("myControllerName", Constants.PRIMITIVE), new ContentDescription(
HelloWorldComponent.class.getName(), null, axis2InitActive, null), myNode);
If you have chosen to use the
PAWebServicesController controller, you do
not need to modified your component instantiation but instead,
before the first exposition, you just have to call the
following method on the controller:
// Deploy the web service servlet on the jetty server corresponding to the node where the component has been // deployed. wsc.initServlet();
Now, let us assume that your component is deployed on a node or locally and that you want to expose it on a host where another node is deployed. On this host, there is only a running Jetty server without any web services servlet. If you are using the web services controller, it is really straight forward. You just have to put a node as an argument of the previous method:
// To expose a component on a remote host where this servlet has not been deployed yet, add // the node started on this host as argument. You can put as many nodes as you want. wsc.initServlet(myNode);
If you are not using the controller, you can use the following line:
WebServicesInitActiveFactory.getInitActive("cxf").initServlet(myNode);
Then, you can expose your component as a web service as in Section 4.5.2.4, “Steps to expose/unexpose a component as a web services using the WebServices interface”.
Once the component interface exposed, you can access it via any web service client (such as C#, SoapUI, Axis2 API, CXF API...) or with your favorite browser (only if the returned type of your service methods are primitive types).
First of all, the client will get the WSDL file matching this
component interface. This WSDL file is the 'identity card' of
the service. It contains the web service public interfaces and
its location. Generally, WSDL files are used to generate a proxy
to the service. For example, for a given service, say
'componentTest_computeItf', you can get the WSDL document at
http://[host]:[port]/proactive/services/componentTest_computeItf?wsdl.
Now that this client knows what and where to call the service, it will send a SOAP message to the web server. The web server looks into the message and performs the right call. Then, it returns the response into another SOAP message to the client.
Contrary to the previous version of the component exposition using Apache SOAP, this new version which uses Axis2 or CXF can handle complex types. That is, with this version, you can implement a service which exposes a method returning or taking in argument an instance of any class. However, the class of this instance has to respect some criterion due to the serialization and Axis2/CXF restrictions:
This class has to be serializable
This class has to supply a no-args constructor and preferably empty
All the fields of this class has to be private or protected
This class has to supply public getters and setters for each field. These getter and setter methods have to be in this class and not in one of its super classes.
Axis2 and CXF are not fully compatible. In particular, clients behave differently. Axis2 client uses to send SOAP requests using "argn" as name for the n-th argument of the requested method. However, CXF creates the WSDL service using parameter names as written into the Java code. Thus, there is a difference of names and the service cannot find argument names in the SOAP message matching with the argument names of the requested method. Null value is therefore used as parameters. To easily solve this issue, you can change the argument names of your service to match with "argn". If you cannot change the service, you have to use another client as those used in the ProActive extension.
If you want to use the CXF client provided by ProActive for calling an Axis2 service, you will also face difficulties since this client waits for a returned SOAP message that the Axis2 service will not return for a void method. One thing but not the best you can do, is to modify ProActive CXF client in order to ignore the exception for oneWayCall. This is not the best solution since other exceptions will be ignored too.
In a general manner, we advise you to use the same framework for the exposition as a web service and for the client consuming this service.
Let's start with a simple example, an Helloworld component with two interfaces exposed as a web service:
First, below are the two server interfaces of the component:
public interface HelloWorldItf {
public String helloWorld(String arg0);
public String sayHello();
public String sayText();
public void setText(String arg0);
}
public interface GoodByeWorldItf {
public String goodByeWorld(String arg0);
public String sayGoodBye();
public void setText(String arg0);
public String sayText();
}
The implementation class of the component is the following one:
public class HelloWorldComponent implements HelloWorldItf, GoodByeWorldItf, Serializable {
/**
*
*/
private static final long serialVersionUID = 51L;
private String text;
public String sayText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public HelloWorldComponent() {
}
public String helloWorld(String arg0) {
return "Hello " + arg0 + " !";
}
public String goodByeWorld(String arg0) {
return "Good Bye " + arg0 + " !";
}
public String sayHello() {
return "Hello ProActive Team !";
}
public String sayGoodBye() {
return "Good bye ProActive Team !";
}
}
The following piece of code creates the component and exposes its interfaces as a web services.
Component componentBoot = Utils.getBootstrapComponent();
GCMTypeFactory typeFactory = GCM.getGCMTypeFactory(componentBoot);
GenericFactory genericFactory = GCM.getGenericFactory(componentBoot);
// type of server component
ComponentType componentType = typeFactory.createFcType(new InterfaceType[] {
typeFactory
.createFcItfType("hello-world", HelloWorldItf.class.getName(), false, false, false),
typeFactory.createFcItfType("goodbye-world", GoodByeWorldItf.class.getName(), false, false,
false) });
// create server component
Component helloWorld = genericFactory.newFcInstance(componentType, new ControllerDescription(
"server", Constants.PRIMITIVE), new ContentDescription(HelloWorldComponent.class.getName()));
//start the component
GCM.getGCMLifeCycleController(helloWorld).startFc();
// If you want the default WebServicesFactory, you can use
// WebServicesFactory.getDefaultWebServicesFactory()
WebServicesFactory webServicesFactory = AbstractWebServicesFactory.getWebServicesFactory("cxf");
// If you want to use the local Jetty server, you can use
// AbstractWebServicesFactory.getLocalUrl() to get its url with
// its port number (which is random except if you have set the
// proactive.http.port variable)
WebServices webservices = webServicesFactory.getWebServices("http://localhost:8080/");
// If you want to expose only the goodbye-world interface for example,
// use the following line instead:
// webservices.exposeComponentAsWebService(helloWorld, "MyHelloWorldComponentService", new String[] { "goodbye-world" });
webservices.exposeComponentAsWebService(helloWorld, "MyHelloWorldComponentService");
If you want to use the web service controller, you should write the following line:
Component componentBoot = Utils.getBootstrapComponent();
GCMTypeFactory typeFactory = GCM.getGCMTypeFactory(componentBoot);
GenericFactory genericFactory = GCM.getGenericFactory(componentBoot);
// type of server component
ComponentType componentType = typeFactory.createFcType(new InterfaceType[] {
typeFactory
.createFcItfType("hello-world", HelloWorldItf.class.getName(), false, false, false),
typeFactory.createFcItfType("goodbye-world", GoodByeWorldItf.class.getName(), false, false,
false) });
// Get the web services controller corresponding to the chosen framework (here cxf)
String controllerPath = AbstractPAWebServicesControllerImpl.getControllerFileUrl("cxf").getPath();
// Create the component using this controller
ControllerDescription controllerDesc = new ControllerDescription("server", Constants.PRIMITIVE,
controllerPath);
// Create server component
Component helloWorldComponent = genericFactory.newFcInstance(componentType, controllerDesc,
new ContentDescription(HelloWorldComponent.class.getName()));
// Start the component
GCM.getGCMLifeCycleController(helloWorldComponent).startFc();
// Get the web services controller
PAWebServicesController wsController = org.objectweb.proactive.extensions.webservices.component.Utils
.getPAWebServicesController(helloWorldComponent);
// Set the Url where the component has to be exposed
wsController.setUrl("http://localhost:8080/");
// Expose the component as a web service
wsController.exposeComponentAsWebService("MyHelloWorldComponentService");
All the interfaces (hello-world and
goodbye-world) of the component
helloWorld have been deployed as web services
on the web server located at
http://localhost:8080/proactive/services/ and
its service names are
"MyHelloWorldComponentService_hello-world" and
"MyHelloWorldComponentService_goodbye-world".
You can see the wsdl files through
http://localhost:8080/proactive/services/MyHelloWorldComponentService_hello-world?wsdl
and
http://localhost:8080/proactive/services/MyHelloWorldComponentService_goddbye-world?wsdl
and you can call the sayHello method of the
hello-world interface with your browser
through
http://localhost:8080/proactive/services/MyHelloWorldComponentService_hello-world/sayHello.
If you have exposed a method which requires arguments, you can
call it using the same link as previously but adding
?[arg0]=[value]&[arg1]=[value]... where
[argn] is the name of the n-th argument as written in the WSDL
file.
You can also call a webservice using the ProActive client which uses, depending on the chosen framework, either an Axis2 or a CXF client. Here is an example:
// Instead of using "cxf", you can also use webServicesFactory.getFrameWorkId()
// in order to be sure to get the same framework as the service
// has used to be exposed. However, you can call a cxf service
// using an axis2 client but there exists some incompatibility
// between these two frameworks.
// If you want the default ClientFactory, you can use
// ClientFactory.getDefaultClientFactory()
ClientFactory cFactory = AbstractClientFactory.getClientFactory("cxf");
// Instead of using "http://localhost:8080/", you can use ws.getUrl() to
// ensure to get to good service address.
Client serviceClient = cFactory.getClient("http://localhost:8080/",
"MyHelloWorldComponentService_goodbye-world", GoodByeWorldItf.class);
// Call which returns a result
Object[] result = serviceClient.call("goodByeWorld", new Object[] { "ProActive Team" }, String.class);
System.out.println((String) result[0]);
// Call which does not return a result
serviceClient.oneWayCall("setText", new Object[] { "Hi ProActive Team!" });
// Call with no argument
result = serviceClient.call("sayText", null, String.class);
System.out.println((String) result[0]);
ProActive also gives the possibility to bind a functional client interface of a component to a web service.
For instance, this feature is useful to enable communication between SCA (Service Component Architecture) components and GCM/ProActive components by using the web service communication protocol. Effectively, communication from SCA component to GCM/ProActive component can be done thanks to the ability of exposing GCM server interface as web service (See Section 4.5.2, “Exporting components as Web Services”) and communication from GCM/ProActive component to SCA component can be done by using this feature. This has already been successfully tested with two SCA implementations: Apache Tuscany and FraSCAti .
The only requirement for binding a client interface to a web service is that the web service must be known in advance in order to ensure the compatibility between the client interface and the web service to bind to. In other words, the Java interface representing the client interface must have the same method signatures than those exposed by the web service.
It is the same principle to bind a client interface to a
web service with both the ADL and the API: the web
service binding can be done just by replacing the server
interface name by the URL of the web service (not the WSDL
address) in the call to the method bindFc
with the API or in the tag binding with
the ADL.
By default, the Axis2 API is used to call the web service, but
it is also possible to specify another library. If so, the URL
has to be followed, in parenthesis, by the ID or the full name
of the class to use to call the web service. Thus, ProActive
offers several ways to call web services: using Axis2 API or
using various configurations of CXF API. IDs are defined in
the class
org.objectweb.proactive.core.component.webservices.WSInfo.
Otherwise, the full class name has to be the one of a class
implementing the
org.objectweb.proactive.core.component.webservices.PAWSCaller
interface:
public interface PAWSCaller {
/**
* Method to setup the caller.
*
* @param serviceClass Class that the web service implements. Should match with
* the client interface to bind to the web service.
* @param wsUrl URL of the web service (not the WSDL address).
*/
public void setup(Class<?> serviceClass, String wsUrl);
/**
* Method to call a web service.
*
* @param methodName Name of the service to call.
* @param args Parameters of the web service.
* @param returnType Class of the return type of the web service. Null if the web
* service does not return any result.
* @return Result of the call to the web service if there is, null otherwise or if the
* invocation failed.
*/
public Object callWS(String methodName, Object[] args, Class<?> returnType);
}
For instance, the replacing string of the server interface name may be:
"http://localhost:8080/proactive/services/Server_HelloWorld(org.objectweb.proactive.core.component.webservices.Axis2WSCaller)"
which is equivalent to: "http://localhost:8080/proactive/services/Server_HelloWorld(" + org.objectweb.proactive.core.component.webservices.WSInfo.AXIS2WSCALLER_ID + ")"
which is also equivalent to: "http://localhost:8080/proactive/services/Server_HelloWorld" since Axis2 is used by default.
In this implementation of the Fractal/GCM component model, Fractal/GCM 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:
the control of the request queue has to use the
org.objectweb.proactive.Service
class
the functional activity has to loop on the
body.isActive() condition.
This is not compulsory but it allows to
automatically end the functional activity when the
lifecycle of the component is stopped. It may also
be managed with a custom filter.
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.
![]() |
Note |
|---|---|
|
By the way, it should be specified that in this
implementation of the
|
Components running in dynamically changing execution environments need to adapt to these environments. In Fractal/GCM component model, adaptation mechanisms are triggered by the non-functional (NF) part of the components. Interactions with execution environments may require complex relationships between controllers. In this section, we focus on the adaptability of the membrane. Examples include changing communication protocols, updating security policies, or taking into account new runtime environments in case of mobile components. Adaptability implies that evolutions of the execution environments have to be detected and acted upon. It may also imply interactions with the environment and with other components for realizing the adaptation.
We provide tools for adapting controllers. These tools manage (re)configuration of controllers inside the membrane. For this, we provide a model and an implementation, using a standard component-oriented approach for both the application (functional) level and the control (NF) level. Having a component-oriented approach for the non-functional aspects also allows them to benefit from the structure, hierarchy and encapsulation provided by a component-oriented approach.
In this section, we propose to design NF concerns as compositions of components as suggested in the GCM proposal. Our general objective is to allow controllers implemented as components to be directly plugged in a component membrane. These controllers take advantage of the properties of component systems like reconfigurability, i.e. changing of the contained components and their bindings. This allows components to be dynamically adapted in order to suit changing environmental conditions. Indeed, we aim at a component platform appropriate for autonomic Grid applications; those applications aim at ensuring some quality of services and other NF features without being geared by an external entity.
Components in the membrane introduce two major changes: first, refinements of the Fractal/GCM model concerning the structure of a membrane; secondly, a definition and an implementation of an API that allows membranes to be themselves composed of components, possibly distributed. Both for efficiency and for flexibility reasons, we provide an implementation where controllers can either be classical objects or full components that could even be distributed. We believe that this high level of flexibility is a great advantage of this approach over the existing ones [MB01, SPC01]. Our model refinements also provide a better structure for the membrane and a better decoupling between the membrane and its externals. Finally, our approach gives the necessary tools for membrane reconfiguration, providing flexibility and evolution abilities. The API we present can be split in two parts:
Methods dedicated to component instantiation: they allow the specification of a NF type of a component and the instantiation of NF components;
Methods for the management of the membrane: they consist in managing the content, introspecting, and managing the life-cycle of the membrane. Those methods are proposed as an extension of the Fractal component model and consequently of the GCM model;
Here we present a simple example that shows the advantages of componentizing controllers of components. In our example, we are considering a naive solution for securing communications of a composite component. As described in Figure 4.9, “Example: architecture of a naive solution for secure communications”, secure communications are implemented by three components inside the membrane: Interceptor, Decrypt, and Alert. The scenario of the example is the following: the composite component receives encrypted messages on its server functional interface. The goal is to decrypt those messages. First, the incoming messages are intercepted by the Interceptor component. It forwards all the intercepted communications to Decrypt, which can be an off-the-shelf component (written by cryptography specialists) implementing a specific decryption algorithm. The Decrypt component receives a key for decryption through the non-functional server interface of the composite (interface number 1 on the figure). If it successfully decrypts the message, the Decrypt component sends it to the internal functional components, using the functional internal client interface (2). If a problem during decryption occurs, the Decrypt component sends a message to the Alert component. The Alert component is in charge of deciding on how to react when a decryption fails. For example, it can contact the sender (using the non-functional client interface – 3) and ask it to send the message again. Another security policy would be to contacta “trust and reputation” authority to signal a suspicious behaviour of the sender. The Alert component is implemented by a developer who knows the security policy of the system. In this example, we have three well-identified components, with clear functionalities and connected through well-defined interfaces. Thus, we can dynamically replace the Decrypt component by another one, implementing a different decryption algorithm. Also, for changing the security policy of the system, we can dynamically replace the Alert component and change its connexions. Compared to a classical implementation of secure communications (for example with objects), using components brings to the membrane a better structure and reconfiguration possibilities. To summarize, componentizing the membrane in this example provides dynamic adaptability and reconfiguration; but also re-usability and composition from off-the-shelf components.
Figure 4.10, “Structure for the membrane of Fractal/GCM components” shows the structure we suggest for
the component membrane. The membrane (in gray) consists of one
object controller and two component controllers, the component
controllers are connected together and with the outside of the
membrane by different bindings. For the moment, we do not specify
whether components are localized with the membrane, or distributed.
Before defining an API for managing components inside the membrane, the definition of the membrane given by the GCM specification needs some refinements. Those refinements, discussed in this section, provide more details about the structure a membrane can adopt. Figure 4.10, “Structure for the membrane of Fractal/GCM components” represents the structure of a membrane and gives a summary of the different kinds of interface roles and bindings that a component can provide. As stated in the GCM specification, NF interfaces are not only those specified in the Fractal specification, which are only external server ones. Indeed, in order to be able to compose NF aspects, the GCM requires the NF interfaces to share the same specification as the functional ones: role, cardinality, and contingency. For example, in GCM, client NF interfaces allow the composition of NF aspects and reconfigurations at the NF level. Our model is also flexible, as all server NF interfaces can be implemented by both objects or components controllers.
All the interfaces showed in Figure 4.10, “Structure for the membrane of Fractal/GCM components” give the membrane a better structure and enforce decoupling between the membrane and its externals. For example, to connect nfc with fns, our model adds an additional stage: we have first to perform binding b3, and then binding b9. This avoids nfc to be strongly coupled with fns: to connect nfc to another fns, only binding b9 has to be changed. In Figure 4.10, “Structure for the membrane of Fractal/GCM components”, some of the links are represented with dashed arrows. Those links are not real bindings but “alias” bindings (e.g. b3); the source interface is the alias and it is “merged” with the destination interface. These bindings are similar to the export/import bindings existing in Fractal (b6, b10) except that no interception of the communications on these bindings is allowed.
While componentizing the membrane clearly improves its programmability and its capacity to evolve, one can wonder what happens to performance. First, as our design choice allows object controllers, one can always keep the efficiency of crucial controllers by keeping them as objects. Second, the overhead for using components instead of objects is very low if the controllers components are local, and are negligible compared to the communication time, for example. Finally, if controllers components are distributed, then there can be a significant overhead induced by the remote communications, but if communications are asynchronous, and the component can run in parallel with the membrane, this method can also induce a significant speedup, and a better availability of the membrane. To summarize, controllers invoked frequently and performing very short treatments, would be more efficiently implemented by local objects or local components. For controllers called less frequently or which involve long computations, making them distributed would improve performances and availability of the membrane.
To typecheck bindings between membranes, we have to extend the GCM
model with a new concept: the non-functional type of a component.
This type is defined as the union of the types of NF interfaces the
membrane exposes. To specify the NF type of a component, we propose
to overload the newFcInstance method (the one to
create functional components) as follows:
public Component newFcInstance(Type fType,Type nfType, any contentDesc, any controllerDesc);
In this method, nfType represents the NF type of the component; it can be specified by hand. Soon, it should be possible to specify the NF type within a configuration file: the controller descriptor argument (controllerDesc) can be a file written in Architecture Description Language (ADL) containing the whole description of the NF system.
Components inside the membrane are non-functional components. They are similar to functional ones. However, their purpose is different because they deal with NF aspects of the host component. Thus, in order to enforce separation of concerns, we restrict the interactions between functional and NF components. For example, a NF component cannot be included inside the functional content of a composite. Inversely, a functional component cannot be added inside a membrane. As a consequence, direct bindings between functional interfaces of NF and functional components are forbidden. In the generic factory, a method named newNFcInstance that creates this new kind of components has been added:
public Component newNFcInstance(Type fType,Type nfType, any contentDesc, any controllerDesc);
Parameters of this method are identical to its functional equivalent and NF components are created in the same way as functional ones.
/**
* Adds non-functional components inside the membrane
* @param component The non-functional component to add
* @throws IllegalContentException If the component to add is not a non-functional component
*/
void addNFSubComponent(Component component) throws IllegalContentException, IllegalLifeCycleException;
/**
* Removes the specified component from the membrane
* @param componentname The name of the component to remove
* @throws IllegalContentException If the specified component can not be removed
*/
void removeNFSubComponent(Component componentname) throws IllegalContentException,
IllegalLifeCycleException, NoSuchComponentException;
/**
* Returns an array containing all the components inside the membrane
* @return All the non-functional components
*/
Component[] getNFcSubComponents();
/**
* Returns the non functional component specified by the name parameter
* @return the non functional component specified by the name parameter
*/
Component getNFcSubComponent(String name) throws NoSuchComponentException;
/**
* Sets a new controller object implementing the specified interface
* @param itf The name of interface the new object has to implement
* @param controllerclass The controller object
* @throws NoSuchInterfaceException If the specified interface doesn't exist
*/
void setControllerObject(String itf, Object controllerclass) throws NoSuchInterfaceException;
/**
* Starts all non functional components inside the membrane
* @throws IllegalLifeCycleException if one of the non functional components is in inconsistent lifecycle state
*/
public void startMembrane() throws IllegalLifeCycleException;
/**
* Stops all non functional components inside the membrane
* @throws IllegalLifeCycleException if one of the non functional components is in inconsistent lifecycle state
*/
public void stopMembrane() throws NoSuchInterfaceException, IllegalLifeCycleException;
Figure 4.11. The primitives for managing the membrane.
To manipulate components inside membranes, we introduce primitives to
perform basic operations like adding, removing or getting a reference
on a NF component. We also need to perform calls on well-known
Fractal/GCM controllers (life-cycle controller, binding controller...)
of these components. So, we extend Fractal/GCM specification by adding
a new controller called membrane controller. As we want it to manage
all the controllers, it is the only mandatory controller that has to
belong to any membrane. It allows the manual composition of membranes
by adding the desired controllers. The methods presented in
Figure 4.11, “The primitives for managing the membrane.” are included in the MembraneController
interface; they are the core of the API and are sufficient to perform
all the basic manipulations inside the membrane. They add, remove, or
get a reference on a NF component. They also allow the management of
object controllers and membrane’s life-cycle. Referring to Fractal,
this core API implements a subset of the behavior of the life-cycle
and content controllers specific to the membrane. This core API can
be included in any Fractal/GCM implementation. Reconfigurations of
NF components inside the membrane are performed by calling standard
Fractal controllers. The general purpose API defines the following
methods:
addNFSubComponent(Component component):
adds the NF component given as argument to the membrane;
removeNFSubComponent(Component component):
removes the specified NF component from the membrane;
getNFcSubComponents(): returns an array
containing all the NF components;
getNFcSubComponent(string name): returns
the specified NF component, the string argument is the name
of the NF component;
setControllerObject(string itf, any controllerclass):
sets or replaces an existing controller object inside the
membrane. Itf specifies the name of the control interface
which has to be implemented by the controller class, given
as second parameter. Replacing a controller object at runtime
provides a very basic adaptivity of the membrane;
startMembrane(): starts the membrane, i.e.
allows NF calls on the host component to be served. This
method has a recursive behavior, by starting the life-cycle
of each NF component inside the membrane;
stopMembrane(): Stops the membrane, i.e.
prevents NF calls on the host component from being served
except the ones on the membrane controller. This method has
a recursive behavior, by stopping the life-cycle of each NF
component.
/**
* Performs bindings inside the membrane, and with non-functional interfaces of internal functional components
* @param clientItf
* The client interface, referenced by a string of the form "component.interface",
* where component is the name of the component, and interface the name of its client interface.
* If the component name is "membrane", it means that interface must be the name of an external/internal interaface from the membrane's type.
* @param serverItf
* The server interface, referenced by a string of the form "component.interface",
* where component is the name of the component, and interface the name of its server interface.
* If the component name is "membrane", it means that interface must be the name of an external/internal interaface from the membrane's type.
* @throws NoSuchInterfaceException If one of the specified interfaces doesn't exist
* @throws IllegalLifeCycleException If one of the component is in inconsistent lifecycle state
* @throws IllegalBindingException If the type of the interfaces doesn't match
*/
void bindNFc(String clientItf, String serverItf) throws NoSuchInterfaceException,
IllegalLifeCycleException, IllegalBindingException, NoSuchComponentException;
/**
* Performs bindings with non-functional external interfaces
* @param clientItf
* The client interface, referenced by a string of the form "component.interface",
* where component is the name of the component, and interface the name of its client interface.
* If the component name is "membrane", it means that interface must be the name of an external interaface from the membrane's type.
* @param serverItf
* The non-functional external server interface
* @throws NoSuchInterfaceException If one of the specified client interface doesn't exist
* @throws IllegalLifeCycleException If one of the component is in inconsistent lifecycle state
* @throws IllegalBindingException If the type of the interfaces doesn't match
*/
void bindNFc(String clientItf, Object serverItf) throws NoSuchInterfaceException,
IllegalLifeCycleException, IllegalBindingException, NoSuchComponentException;
/**
* Removes non-functional bindings
* @param clientItf
* The client interface that will have its binding removed. It is a string of the form "component.interface",
* where component is the name of the component, and interface the name of its server interface.
* If the component name is "membrane", it means that interface must be the name of an external/internal interaface from the membrane's type.
* @throws NoSuchInterfaceException If the client interface doesn't exist
* @throws IllegalLifeCycleException If the component is in inconsistent lifecycle state
* @throws IllegalBindingException If the binding can not be removed
*/
void unbindNFc(String clientItf) throws NoSuchInterfaceException, IllegalLifeCycleException,
IllegalBindingException, NoSuchComponentException;
/**
* Returns the names of the client interfaces belonging to the component, which name is passed as argument
* @param component The name of the component
* @return An array containing the name of the client interfaces(we suppose that non-functional components don't have client NF interfaces).
*/
String[] listNFc(String component) throws NoSuchComponentException, NoSuchInterfaceException,
IllegalLifeCycleException;
/**
* Returns the stub and proxy of the server interface the client interface is connected to (for components inside the membrane)
* @param itfname
* The client interface, referenced by a string of the form "component.interface",
* where component is the name of the component, and interface the name of its client interface.
* @return the stubs and the proxy of the server interface the client interface is connected to
* @throws NoSuchInterfaceException If the specified interface doesn't exist
*/
Object lookupNFc(String itfname) throws NoSuchInterfaceException, NoSuchComponentException;
/**
* Starts the specified component
* @param component The name of the component to stop
* @throws IllegalLifeCycleException If the lifecycle state is inconsistent
*/
void startNFc(String component) throws IllegalLifeCycleException, NoSuchComponentException,
NoSuchInterfaceException;
/**
* Stops the specified component
* @param component The name of the component to stop
* @throws IllegalLifeCycleException If the lifecycle state is inconsistent
*/
void stopNFc(String component) throws IllegalLifeCycleException, NoSuchComponentException,
NoSuchInterfaceException;
/**
* Returns the state of the specified non-functional component
* @param component The name of the component
* @return The current state of the specified component
*/
String getNFcState(String component) throws NoSuchComponentException, NoSuchInterfaceException,
IllegalLifeCycleException;
/**
* Returns the state of the membrane (by default, started or stopped).
* @return The current state of the membrane
*/
String getMembraneState();
Figure 4.12. Higher level API
In Figure 4.12, “Higher level API”, we present an alternative API, that addresses NF components by their names instead of their references. These methods allow to make calls on the binding controller and on the life-cycle controller of NF components that are hosted by the component membrane. Currently, they do not take into account the hierarchical aspect of NF components. The method calls address the NF components and call their controllers at once. For example, here is the Java code that binds two components inside the membrane using the general purpose API. It binds the interface “i1” of the component “nfComp1” inside the membrane to the interface “i2” of the component “nfComp2”. Suppose "mc" is a reference to the membrane controller of the host component.
Component nfComp1=mc.getNFcSubComponent("nfComp1");
Component nfComp2=mc.getNFcSubComponent("nfComp2");
GCM.getBindingController(nfComp1).bindFc("i1",nfComp2.getFcInterface("i2"));
Using the API of Figure 4.12, “Higher level API”, this binding can be realized by the following code, that binds the component “nfComp1” correctly.
mc.bindNFc("nfComp1.i1","nfComp2.i2");
Similarly to the example above, all the methods of
Figure 4.12, “Higher level API” result in calls on well-known Fractal controllers.
Interfaces are represented as strings of the form component.interface, where
component is the name of the inner component and interface is the name of
its client or server interface. We use the name “membrane” to represent the
membrane of the host component, e.g. membrane.i1 is the NF interface i1 of
the host component; in this case interface is the name of an interface from
the NF type. For example, bindNFc(string, string)
allows to perform the bindings: b1,
b2, b4, b3,
b9, b7 and b5
of Figure 4.10, “Structure for the membrane of Fractal/GCM components”.
The API presented in Figure 4.12, “Higher level API” introduced higher level
mechanisms for reconfiguring the membrane. It also solves the problem of local
components inside the membrane. As usual in distributed programming paradigms,
GCM objects/components can be accessed locally or remotely. Remote references
are accessible everywhere, while local references are accessible only in a
restricted address space. When returning a local object/component outside its
address space, there are two alternatives: create a remote reference on this
entity; or make a copy of it. When considering a copy of a NF local component,
the NF calls are not consistent. If an invocation on
getNFcSubComponent(string name) returns a copy of the
specified NF component, calls performed on this copy will not be performed
on the “real” NF component inside the membrane. Methods introduced in
Figure 4.12, “Higher level API” solve this problem.
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 figure, a simple component system, which consists of a composite containing two wrapped primitive components, is represented with different distributions of the components. In a, all components are located in the same JVM, therefore all communications are local communications. If the wrapping composites are distributed on different remote JVMs, all communications are remote because they have to cross composite enclosing components. The short cut optimization is a simple bypassing of the wrapper components, which results in 2 local communications for the sole functional interface.
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 short cuts between components whenever possible.
© 1997-2010 INRIA/University of Nice-Sophia Antipolis/ActiveEon All Rights Reserved