Service references
A very powerful feature of the component system is the declarative expression of references to other services registered in the App Runtime. The declarative component model significantly simplifies the handling of the dynamics of service dependencies.
A reference expression is a declarative wrapper of the method BundleContext.getServiceReferences
(described in Programmatic service access), which requires an interface as selector together with an optional filter expression.
Services which are selected by these conditions are called target services.
The properties providing
, filter
and cardinality
of a reference specify rules which are checked by the App Runtime.
If all rules of a reference are fulfilled, the reference is said to be satisfied.
A component configuration becomes satisfied when all references are satisfied.
If a reference becomes unsatisfied, the component configuration also becomes unsatisfied and is deactivated.
If a component configuration is activated by the App Runtime, some of the target services are bound to the configuration.
These services are called bound services.
References are expressed as part of the component description.
manifest.json
file: references of components{
"name" : "mybundle",
...
"components" : [
{
"name" : "MyComponent",
...
"references" : [
{
"name" : "logService",
"providing" : "ct.framework.api.LogService"
},
...
]
},
...
]
}
A reference needs at least a name
and a providing
property.
The name
property must have a unique value inside the references of the component description and identifies the reference.
The providing
property defines the interface which the target service must provide.
Filtering of References
The property filter
at a reference is used to further reduce the target services selected by the providing property.
The value of filter
is a filter expression described in the section Searching for bundles.
{
"name" : "myFactory",
"providing" : "ct.framework.api.ComponentFactory",
"filter" : "(Component-Factory=MyComponentFactory)"
}
It is allowed to use property placeholders in filter expressions, like in the following code sample.
properties: {
"storeId" : "sample-store"
},
references:[{
"name": "store",
"providing": "ct.api.Store",
// the string '{storeId}' is replaced by the value of the property 'storeId' of the component configuration
"filter": "(&(useIn=selection)(id={storeId}))"
}]
Placeholders in filter expressions allow the change of the target set of a reference based on configuration properties.
Reference Cardinality
By specifying the property "cardinality"
on a reference you can declare target set conditions.
{
"name" : "myFactory",
"providing" : "myservice.Interface",
// optional reference, only one target service needed
"cardinality" : "0..1"
}
Following cardinality values are supported:
Expression | Description |
---|---|
|
Selects exactly one target service from the set of target services and a target service must be available to become satisfied. This is the default if no property is defined. |
|
The reference is satisfied if none or more target services are available. Selects one service from the set of target services, if available. |
|
Selects all target services from the set of target services and a target service must be available to become satisfied. |
|
The reference is satisfied if none or more target services are available. Selects all services from the set of target services, if available. |
If only one target service is selected then the first service returned by BundleContext.getServiceReferences
is bound, which is the one with the highest ranking or the one first registered.
Reference Policy
A special lifecycle property is the "policy"
property on a reference.
It can have the value "dynamic"
or "static"
and declares the capability of a component instance to react on runtime changes of the matching target services of a reference.
The policy is only important if changes in the target services still keep the reference satisfied.
For example, this is the case if a reference has a 1..n
cardinality and a new target service is registered at the system.
Value | Description |
---|---|
|
Defines that the component instance is able to react dynamically on changes in the bound service set itself. Therefore the App Runtime is not restarting the component configuration. This is the default value. |
|
Defines that the component instance is not able to react on dynamic changes in the bound service set. This means that the App Runtime restarts the component configuration on such changes. |
{
"name" : "myFactory",
"providing" : "myservice.Interface",
"cardinality" : "0..n",
// defines that the component cannot handle dynamic changes of that reference
"policy" : "static"
}
Accessing References
The bound service set of declared references are provided by the App Runtime to a component instance in different ways. The App Runtime distinguishes between following main strategies:
-
Injection Strategy: References are transparently injected into the component instance and access is made via member variables in the component instance.
-
Event Strategy: References are injected into the component instance by method calls. The App Runtime calls specific methods provided by the component instance to inform about new services and service removal.
-
Lookup Strategy: References are manually looked up by the component instance in the component context.
All strategies can be mixed inside a component instance, very common is a mix of Injection Strategy and Event Strategy. The Lookup Strategy is rarely used.
All these strategies are discussed in the following sections on this sample component description:
{
"name" : "mybundle",
...
"components" : [
{
"name" : "MyComponent",
"references" : [
{
"name" : "logService",
"providing" : "ct.framework.api.LogService"
},
{
"name" : "stores",
"providing" : "ct.api.Store",
"cardinality" : "0..n"
}
]
}
]
}
Accessing References via Injection Strategy
The injection strategy means that references are provided as member properties inside the component instance without any further effort on the side of the component instance.
The component instance can directly access the injected properties.
The injected properties are named exactly like the name property of the reference.
To access the service properties of an injected reference, the special property <name>_info
is injected.
export default class {
doSomething(){
// simply access the logService service instance,
// because of the default 1..1 cardinality it is guaranteed to be available
this.logService.info("DoSomething started!");
// read service properties
let logserviceProperties = this.logService_info;
// this.stores contains all stores in the system (0..n cardinality)
// if no stores are in the system, the array is still present but empty
let stores = this.stores;
// read service properties of stores, is an array, too
let storeProperties = this.stores_info;
stores.forEach((store,index)=>{
// get service properties of current store
let storeProps = storeProperties[index];
let ranking = storeProps["Service-Ranking"];
...
});
}
}
Injection/ejection happens before activate and after deactivate and of course in between if the policy is dynamic and the target service set changes.
For details, see component lifecycle methods.
|
Accessing References via Event Strategy
The event strategy is used by the App Runtime to inject services into the component instance if the instance provides event methods. The names of the event methods are calculated based on the multiplicity of the cardinality and the name of the reference.
Cardinality | Name Calculation Pattern | Sample |
---|---|---|
|
|
|
|
|
|
The name of the reference is capitalized to create the method name, so logService is converted into LogService .
|
export default class {
constructor(){
this.logger = null;
this.stores = [];
}
// called by App Runtime to bind the log service
setLogService(logservice, serviceproperties){
this.logger = logservice;
}
// called by App Runtime to inform about unregistration of the log service
unsetLogService(logservice, serviceproperties){
this.logger = null;
}
// called by App Runtime to inform about a new store
addStores(store, serviceproperties){
this.stores.push(store);
}
// called by App Runtime to inform about unregistration of a store
removeStores(store, serviceproperties){
var index = this.stores.indexOf(store);
this.stores.splice(index,1);
}
doSomething(){
this.logger.info("DoSomething started!");
this.stores.forEach((store)=>{
...
});
}
}
These default method names can be overwritten by defining the "bind"
and "unbind"
properties at the reference description.
{
"name" : "mybundle",
...,
"components" : [
{
"name" : "MyComponent",
"references" : [
{
"name" : "logService",
"providing" : "ct.framework.api.LogService",
// declare custom event methods (setLogging is used for bind and unbind)
"bind" : "setLogging",
"unbind" : "setLogging"
}
]
}
]
}
This results in an implementation like this:
export default class {
constructor(){
this.logger = null;
}
// called by App Runtime to bind and unbind the log service
setLogging(logservice, serviceproperties){
if (this.logger){
// unset
this.logger = null;
}else{
this.logger = logservice;
}
}
doSomething(){
this.logger.info("DoSomething started!");
}
}
The event methods are called (like injection) before activate and after deactivate and of course also between if the policy is dynamic and the target service set changes.
For details, see component lifecycle methods.
|
Accessing References via Lookup Strategy
A third variant for retrieving the services is to use the ComponentContext.
The ComponentContext is provided by the activate
method (see component lifecycle methods).
Reference-relevant methods of the ComponentContext are:
Method | Description |
---|---|
|
Returns an array of service references. The matching target services. |
|
Returns an array of service instances. The bound service set. |
|
Returns service instance selected by the service reference. |
The usage style is similar to working with the BundleContext.
Services, which are located by using locateService
or locateServices
, should not be stored in member variables.
The 'locate*' methods should only be called between activate
and deactivate
.
For details, see component lifecycle methods.
export default class {
activate(componentContext){
// store component context in member variable
this.componentContext = componentContext;
}
deactivate(componentContext){
// clear member variable
this.componentContext = null;
}
doSomething(){
let componentContext = this.componentContext;
// cardinality is 1..1 so the result array has exactly 1 item, the log service
let logger = componentContext.locateServices("LogService")[0];
logger.info("DoSomething started!");
// get all stores
let stores = componentContext.locateServices("Stores");
stores.forEach((store)=>{
...
});
}
}
If the lookup style is used in a component instance, the special property "noInjection"
should be set on the reference description to ensure that the App Runtime does not inject the references into the component instance.
{
"name" : "mybundle",
...,
"components" : [
{
"name" : "MyComponent",
"references" : [
{
"name" : "logService",
"providing" : "ct.framework.api.LogService",
// disable injection behavior
"noInjection" : true
},
{
"name" : "stores",
"providing" : "ct.api.Store",
"cardinality" : "0..n",
// disable injection behavior
"noInjection" : true
}
]
}
]
}
Declarative Event Binding to References
A further feature of the component system is the declarative event binding. This is of interest if a component instance likes to listen on events on a reference and to execute a special method if that event is fired.
It can be distinguished between three kinds of event binding styles: "connect", "on", and "watch".
Style: on
This style uses the dojo/on
module to connect to events provided by services.
The "on"
property on a reference declares the binding.
{
"name" : "aTool",
"providing" : "ct.tools.Tool",
"filter" : "(id=atoolname)",
// on is an object with event names to event handle methods
"on" : {
// Click = event name fired by tool
// _handleOnClick = the method which should be called if the event fires
"Click" : "_handleOnClick"
}
}
The code of the component instance might look like this:
export default class {
_handleOnClick(event){
// do something...
console.debug("clicked..", event);
}
}
Style: connect
This style should be replaced by using the on style, when possible. |
This style uses the dojo/_base/connect
module to connect to events provided by services.
The "connect"
property on a reference declares the binding.
{
"name" : "aTool",
"providing" : "ct.tools.Tool",
"filter" : "(id=atoolname)",
// connect is an object with event names to event handle methods
"connect" : {
// onClick = event fired by tool
// _handleOnClick = the method which should be called if the event fires
"onClick" : "_handleOnClick"
}
}
The code of the component instance might look like this:
export default class {
_handleOnClick(event){
// do something...
console.debug("clicked..", event);
}
}
Style: watch
This style declares a property change listener.
It can be used at services which provides watch
methods, like defined by dojo/Stateful
.
All ct/Stateful
and dijit/_Widget
instances provide such a method.
The "watch"
property on a reference declares the binding.
{
"name" : "aStateful",
"providing" : "a.datamodel.Instance",
// watch is an object with property names to property change listener methods
"watch" : {
// selectedItem = name of the property about whose value changes should be informed
// _handleSelectedItemChange = the property change listener method
"selectedItem" : "_handleSelectedItemChange"
}
}
The code of the component instance might look like this:
export default class {
_handleSelectedItemChange(propertyname, oldvalue, newvalue){
// do something...
}
}