Declarative Components

This section describes the JavaScript adoption of the OSGi Declarative Services Specification.

The goal of the Declarative Service Specification is to hide the complexity of the low level programming approach provided by the dynamic service layer to a more descriptive and easier way using metadata declarations. A lot of the dynamic complexity is hidden behind simple configuration statements.

The Declarative Service Specification introduces a new term "Component", or in other words a component-oriented approach to declaring dynamic services. Sometimes it is difficult to understand the difference between a component and a service. In the book "OSGi in Action" (Manning 2011 - ISBN 9781933988917, page 119) the author points out that both terms refer to the same thing, but from a different architectural perspective. When talking about components, the focus is on the view of the provider. This means, in turn, that when talking about services, the focus is on the customer’s point of view. In the following numerous JSON component declarations are listed. The purpose of these declarations is to declare a service to be registered with the JavaScript OSGi runtime, so that the focus is on the service provider view.

The main advantages of declarative services are:

  • Service/Component lifecycle management

  • Declarative expression of dependencies on services (dependency injection)

  • Simplified implementation of services (focus on functional tasks and not on managing dependencies)

  • Simple properties-based configuration model

The Declarative Component System can be imagined as an extension layer built upon the dynamic service layer.

Components

Terms used in following sections:

Component

A component contains a description that is interpreted at runtime to create and dispose objects depending on the availability of other services, the need for such an object, and available configuration data. Such objects can optionally provide a service.

Component Description

The declaration of a component. It is contained within the manifest.json of a bundle.

Component Properties

A set of properties which can be specified by the component description, the Configuration Admin Service and from the component factory.

Component Configuration

A component configuration represents a component description parameterized by component properties. It is the entity that tracks the component dependencies and manages a component instance. An activated component configuration has a component context.

Component Instance

An instance of the component implementation class. A component instance is created when a component configuration is activated and discarded when the component configuration is deactivated. A component instance is associated with exactly one component configuration.

Delayed Component

A component whose component configurations are activated when their service is requested.

Immediate Component

A component whose component configurations are activated immediately upon becoming satisfied.

Factory Component

A component whose component configurations are created and activated through the component’s component factory.

Reference

A specified dependency of a component on a set of target services.

Service Component Runtime (SCR)

The actor that manages the components and their lifecycle.

Target Services

The set of services that is defined by the reference interface and filter property.

Bound Services

The set of target services that are bound to a component configuration.

The life of a component is fully managed by a component environment which is called the Service Component Runtime (SCR). The SCR is provided and registered to the JS OSGi runtime by the system bundle. A component instance is a normal JavaScript Object, which can provide some lifecycle methods.

The SCR distinguishes between the following component types:

Immediate Component

The component configuration of an immediate component is activated immediately after becoming satisfied. Immediate components can provide a service.

Delayed Component

When a component configuration of a delayed component becomes satisfied, the SCR registers the service specified by the service element without creation of the component instance. If this service is requested, the SCR creates the component instance and returns it as the service object. If the serviceFactory attribute of the component description is true, for each distinct bundle that requests the service, a different component instance is created and returned as the service object.

Factory Component

If a component’s description specifies the componentFactory attribute, the SCR registers a Component Factory service. This service allows client bundles to create and activate multiple component configurations and dispose of them. If the component’s description also specifies a service, as each component configuration is activated, the SCR registers it as a service.

Declaring a Component

A component description is provided inside the manifest.json in the special property "components".

Components section in manifest.json
{
  "name" : "mybundle",
  ...
  "components" : [
  {
    "name" : "MyComponent"
  },
  ...
  ]
}

A full list of all available properties inside a component description can be found at the end of this chapter.

A component description must at least contain the name of the component. This name must be unique inside the bundle.

Specifying the implementation class

By default the name of the component is used as implementation class name. If a different name should be used, the property "impl" must be specified.

Components: impl property
{
  "name" : "MyComponent",
  "impl" : "AClass"
}

The implementation class of a component is located exactly like described in the "Class Loading" section of the chapter Module layer, the SCR tries to resolve the class by looking into the object returned by the module.js.

Providing a Service

A component instance is registered as a service in the JS OSGi runtime if its component description declares the property "provides". The property contains the names of the provided service interfaces, the value can be a string or an array.

Components: provides property as string
{
  "name" : "MyComponent",
  "provides" : "myservice.Interface"
}

or

Components: provides property as array
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface","asecondprovided.Interface"]
}

The properties specified in the components description are also important for service registration, see Component Properties. It is important that each property which does not start with a _ prefix is treated as public service property and registered as service properties.

Instance Factory

Sometimes it is required to register a different instance as a service than the one that you want to configure as a component implementation. This is often the case if you have a ready-to-use class which is implemented independent of the component model and you don’t wish to introduce component system-relevant code inside this class, for example configuration code. A very typical use case is the use of Dijit widgets as components. To support this case the component description has to be marked as "instanceFactory", which means that the component instance acts as factory for the service.

Components: instanceFactory property
{
  "name" : "MyComponentFactory",
  "provides" : ["myservice.Interface"],
  "instanceFactory": true
}

A component instance which was marked as an instance factory must implement the method createInstance, which is responsible for creating the "real" service instance. This service instance is registered at the JS OSGi runtime. Optionally the component instance can provide a destroyInstance method, which is responsible for cleaning up the service instance during shutdown.

Components: instanceFactory sample
import MyComponentWidget from "./MyComponentWidget";
export default class{

    // responsible to create the service
    createInstance(){
      return new MyComponentWidget();
    },

    // responsible to destroy the service
    destroyInstance(instance){
      // destroys the service
      instance.destroyRecursive();
    }
}

Service Factory

The component system also supports the service factory pattern, as described in the Dynamic Services section. If the serviceFactory flag is set to true in the component description, for each distinct bundle a new service component instance is created and returned to the using bundle. Only delayed components are allowed to be service factories, because for all other types of components the SCR is not free to create the component instance on demand.

Components: serviceFactory property
{
  "name" : "MyComponentFactory",
  "provides" : ["myservice.Interface"],
  // this component should behave as service factory
  "serviceFactory": true
}

Immediate Component

An immediate component is declared if the component description contains the property "immediate" with value 'true' and is not a factory component. A component is treated as immediate if its description does not declare a "provides" property.

Immediate Component
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface"],

  // this component should immediatly created
  "immediate": true
}

Immediate component instances are created immediately if all references are satisfied. If the component has no references, you can consider the lifetime of the component as equal to BundleActivator, which are created on the start of a bundle and destroyed during the stop of the bundle. Only immediate components are allowed to act asynchronously in their activation method. This means a component instance is allowed to return a dojo.Deferred during its activate method and the SCR waits until activate finishes before it registers the component instance as a service at the JS OSGi runtime.

Delayed Component

A component description which declares a "provides" property and which is not an immediate component and not a factory component is treated as delayed. This means that delayed components are the most common kind of components, because this is the default case if no other special property is declared.

Delayed Component
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface"]
}

Delayed components are so named because of the fact that service registration is made before the component instance is created. This means that the creation of the component instance is delayed until the first use of the registered service. A delayed component instance is destroyed if it becomes unused. As a result, delayed components can have a very dynamic life because construction and destruction might happen very often and are highly dependent on usage of the service provided by the component.

Factory Component

A component description which specifies the property "componentFactory" is treated as factory component.

Factory Component
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface"],
  "componentFactory" : true
}

A factory component is a special design pattern, because it acts more as a template for the dynamic creation of component configurations based on component descriptions. The SCR parses the component description. If it is satisfied, it registers a service with the interface ct.framework.api.ComponentFactory for this configuration. This service provides a method newInstance, which can be used to create new component configurations during runtime. The newInstance method returns a ComponentInstance object, which provides the method getInstance for accessing the real instance and the method dispose for shutting down the component configuration. The caller of newInstance is responsible for the disposal of the created component configuration.

Usage of a Factory Component
// Activator using a component factory
export default class{
  start(bundleContext){
    // search special component factory with name "MyComponentFactory"
    let references = bundleContext.getServiceReferences("ct.framework.api.ComponentFactory","(Component-Name=MyComponent)");
    try{
      // get the component factory service
      let factory = bundleContext.getService(references[0]);

      // create a new component configuration
      // here the default component properties defined in the declaration of the component factory can be overwritten.
      let componentInstance = factory.newInstance({ "aproperty": "newValue" });

      // get component/service implementation
      // this feature should rarely be used, better define a reference
      let impl = componentInstance.getInstance();

      impl.doSomething();

      // the created component configuration has to be cleaned up later
      componentInstance.dispose();

    }finally{
      bundleContext.ungetService(references[0]);
    }
  }
}

Factory Components are a very powerful feature because the newInstance method creates new component configurations. This means that such a new dynamically created component configuration is managed by the SCR as if it were a new and independent component description in the manifest.json. Therefore if the property "provides" was specified, the new component instance is registered as a service in the JS OSGi runtime.

Because of the ComponentInstance.getInstance method, a component configuration created by a factory component is treated as an immediate component, therefore, regarding its using state, the component instances are directly created. An important point is that a factory component is satisfied if all references of the component description are also satisfied. In some circumstances this definition leads to unexpected behaviors especially if references are using placeholders in filter expressions.

Usage of a Factory Component in other Component
// Other component using a component factory

// manifest.json
{
   "name" : "OtherComponent",
   "references" : [{
        "name" : "_factory",
        "providing" : "ct.framework.api.ComponentFactory",
        "filter": "(Component-Name=MyComponent)"
    }]
}

// implementation of OtherComponent
export default class{
  activate(){
      // create a new component configuration
      // here the default component properties defined in the declaration of the component factory can be overwritten.
      let componentInstance = this._factory.newInstance({ "aproperty": "newValue" });

      // get component/service implementation
      // this feature should rarely be used, better define a reference
      let impl = componentInstance.getInstance();

      impl.doSomething();

      // the created component configuration has to be cleaned up later
      componentInstance.dispose();
  }
}

Component Properties

A component description can declare properties. These properties are used for two purposes. The first is to provide a configuration mechanism and the second to specify service properties (if the "provides" property is defined).

Components: properties
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface"],
  // properties of the component
  "properties": {
    // underscore properties are private properties
    "_offset" : 125,
    // all other properties are registered as service properties (if provides exist)
    "url" : "http://locahost:8080/..."
  }
}

The recommended way to access component properties within an implementation class is to use the provided property _properties. This is a plain JavaScript object which can be used to access the configuration properties.

Components: injected properties
export default class {
    aServiceMethod(){
      // accessing the injected '_properties' object
      let properties = this._properties;
      // the url property
      let url = properties.url;
      // the offset property
      let offset = properties._offset;
      ...
    }
}

An alternative way is using the Component Context provided by the activate method (see component lifecycle). The Component Context provides a getProperties method, which returns a ct.Hash instance containing the properties, which means that the access style is slightly different.

Components: programmatic properties
export default class {
    activate(componentContext){
      // get the properties from ComponentContext
      var properties = componentContext.getProperties();
      // the url property
      var url = properties.get("url");

      // the offset property
      var offset = properties.get("_offset");
      ...
    }
}

Another way to retrieve the properties is to specify the flag "propertiesConstructor" on the component description. This defines that the configuration properties are transported as constructor options into the implementation class. Here the properties are exactly like the '_properties' property a plain JavaScript object. Besides the transportation as constructor properties, the configuration properties are still injected as _properties property.

Components: propertiesConstructor
{
  "name" : "MyComponent",
  "provides" : ["myservice.Interface"],

  // the component implementation shall get the properties as constructor options
  "propertiesConstructor": true,
  "properties": {
    "_offset" : 125,
    "url" : "http://locahost:8080/..."
  }
}
Components: properties constructor access
export default class {
    constructor(properties){
      // the url property
      let url = properties.url;

      // the offset property
      let offset = properties._offset;
      ...
    }
  });
});

Configuration properties are read only. You are not allowed to change them.

Visibility of Properties

Properties can be public or private. Public properties are part of the component interface and registered as service properties if the component is registered as OSGi service.

By default all properties starting with an underscore "_" are treated as private properties, which mean that all other properties are registered as public service properties.

Following further declaration options are possible:

Components: Visibility Declarations
// 1) use +|- prefix declarations
{
    "name": "MyComponent",
    "provides" : "anInterface",
    "properties": {
        // public    -- use the "+" prefix to declare a public property.
        "+id": "test",
        // private   -- use the "-" prefix to declare a private property.
        "-aConfigOption" : "optionvalue"
    }
}

// 2) use publicProperties and/or privateProperties declarations
{
    "name": "MyComponent",
    "provides" : "anInterface",
    "properties": {
        "id": "test",
        "aConfigOption" : "optionvalue"
    },
    "publicProperties" : ["id"],
    "privateProperties" : ["aConfigOption"]
}

If you use the +…​ or "publicProperties" variant, all other properties are private. If you use the -…​ or "privateProperties" variant, only the declared properties are private.

You are free to mix the styles.

Accessing Internationalization (i18n) Properties

A component instance can access the i18n properties provided by the bundle using the "_i18n" property, injected into it. The "_i18n" property is a ct.I18N object. The "_i18n" property is also provided in the constructor if the flag 'propertiesConstructor' is set to 'true'.

Components: access i18n
export default class {
    aServiceMethod(){
      // get a i18n value of key 'i18nProperty'
      let i18nValue = this._i18n.get().i18nProperty;
      ...
    }
}

Accessing the BundleContext

Each component implementation is allowed to access the BundleContext. This can be achieved by providing a activate method (see component lifecycle). This method is called with a single parameter called ComponentContext from which a component instance can get the BundleContext.

Components: getBundleContext
export default class {
    activate(componentContext){
      // retrieve the BundleContext from the component context
      let bundleContext = componentContext.getBundleContext();
    }
}
If a component instance stores the BundleContext or ComponentContext as member variable, it has to release these resources during destruction, therefore it has to implement a deactivate method!

References to Services

A very powerful feature of the component system is the declarative expression of references to other services registered in the JS OSGi 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 the Dynamic Services chapter), 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 SCR. 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 SCR, 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: 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" in chapter "Module layer".

References filter property
{
  "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 sample.

References filter property
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 it is possible to declare target set conditions.

References cardinality property
{
  "name" : "myFactory",
  "providing" : "myservice.Interface",
  // optional reference, only one target service needed
  "cardinality" : "0..1"
}

Following cardinality values are supported:

Expression Description

1..1

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.

0..1

The reference is satisfied if none or more target services are available. Selects one service from the set of target services, if available.

1..n

Selects all target services from the set of target services and a target service must be available to become satisfied.

0..n

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

dynamic

Defines that the component instance is able to react dynamically on changes in the bound service set itself. Therefore the SCR is not restarting the component configuration.

This is the default value.

static

Defines that the component instance is not able to react on dynamic changes in the bound service set. This means the SCR restarts the component configuration on such changes.

References policy property
{
    "name" : "myFactory",
    "providing" : "myservice.Interface",
    "cardinality" : "0..n",
    // defines that the component can't handle dynamic changes of that reference
    "policy" : "static"
}

Accessing References

The bound service set of declared references are provided by the SCR to a component instance in different ways. The SCR 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 SCR 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:

Component description sample
{
    "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.

Reference injection
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.

Accessing References via Event Strategy

The event strategy is used by the SCR 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

0..1 or 1..1

set<ReferenceName>

unset<ReferenceName>

setLogService : function(logservice, serviceproperties){ …​ }

unsetLogService : function(logservice, serviceproperties){ …​ }

0..n or 1..n

add<ReferenceName>

remove<ReferenceName>

addStores : function(store, serviceproperties){ …​ }

removeStores : function(store, serviceproperties){ …​ }

The name of the reference is capitalized to create the method name, so logService is converted into LogService.
Reference Event Strategy
export default class {
        constructor(){
            this.logger = null;
            this.stores = [];
        }

        // called by SCR to bind the log service
        setLogService(logservice, serviceproperties){
            this.logger = logservice;
        }

        // called by SCR to inform about unregistration of the log service
        unsetLogService(logservice, serviceproperties){
            this.logger = null;
        }

        // called by SCR to inform about a new store
        addStores(store, serviceproperties){
            this.stores.push(store);
        }

        // called by SCR 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.

Reference with custom bind
{
    "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:

Reference with custom bind method
export default class {

        constructor(){
            this.logger = null;
        }

        // called by SCR 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.

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).

Reference-relevant methods of the ComponentContext are:

Method Description

locateServiceReferences : function(referenceName)

Returns an array of service references. The matching target services.

locateServices : function(referenceName)

Returns an array of service instances. The bound service set.

locateService : function(referenceName, serviceReference)

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.

References via lookup
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 SCR does not inject the references into the component instance.

References: noInjection property
{
    "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.

References policy property
{
    "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:

MyComponent implementation using injected properties
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.

References policy property
{
    "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:

MyComponent implementation using injected properties
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.

References policy property
{
    "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:

MyComponent implementation using injected properties
export default class {

    _handleSelectedItemChange(propertyname, oldvalue, newvalue){
        // do something...
    }
}

Component Lifecycle

Enabled

A component must be enabled before it can be used. You can define the initial value of "enabled" in the component description as property "enabled" the default value is true, so by default components are enabled.

Component enabled property
{
    "name" : "MyComponent",
    "provides" : ["myservice.Interface"],

    // disables the component
    "enabled": false
}

You can programmatically control this state using the methods enableComponent and disableComponent of the ComponentContext. A component can only enable/disable the components within its own bundle.

The next sample shows how a component can be used to enable/disable another component (ToolXY).

programmatic enable/disable
export default class {

        activate(componentContext){
            this._componentContext = componentContext;
        }
        deactivate(){
            this._componentContext = null;
        }

        enableToolXY(){
            this._componentContext.enableComponent("ToolXY");
        }
        disableToolXY(){
            this._componentContext.disableComponent("ToolXY");
        }
}

Satisfied

The SCR activates a component configuration only if the component configuration is satisfied. The following conditions must be true for a component configuration to become satisfied:

  • The component is enabled.

  • All the references are satisfied. A reference is satisfied when the reference specifies optional cardinality or there is at least one target service for the reference.

As soon as any of the listed conditions are no longer true, the component configuration becomes unsatisfied and is deactivated.

Lifecycle States

The lifecycle state diagram of an immediate component looks like this:

immediate component states

If an immediate component becomes satisfied, it is activated, and if it provides a declaration, it is registered as a service in the JS OSGi runtime. It remains in ACTIVE state until it becomes unsatisfied, which triggers the unregistration and deactivation of the component.

The lifecycle state diagram of a delayed component looks like this:

delayed component states

If a delayed component becomes satisfied, it is first registered as service in the JS OSGi runtime. When the first client gets the registered service, the component is activated. The component remains in active state until it becomes unsatisfied or if the last client "ungets" the service instance, which triggers the deactivation of the component. If unget triggered the deactivation, the component remains registered. Otherwise it becomes unsatisfied.

The lifecycle state diagram of a factory component looks like this:

factory component states

If a factory component becomes satisfied, the factory service is created and registered at the JS OSGi runtime. The factory service is registered until it becomes unsatisfied. If a client calls newInstance on the factory, a new component configuration is created and activated. The lifecycle of a component created by a factory is equal to the lifecycle of an immediate component.

Lifecycle Methods

A component instance can provide following lifecycle methods.

Name Description

init(componentContext)

Signals the start of the injection phase of the component. It is the first method called by the SCR after the construction of the instance. Services are injected after this method is called, but the special properties _properties and _i18n are ^already usable.

activate(componentContext)

Signals the end of the injection phase and that the component instance is ready for use. The SCR guarantees that service consumers can access the component instance only after activate is called on the instance.

[NOTE] Only immediate components are allowed to return a dojo.Deferred instance in the activate method to delay the following processes until the deferred is resolved.

createInstance()

This method is called after activate if "instanceFactory" was set to true.

modified(componentContext)

Signals that the component properties(configuration) was changed. This method is only called between activate and deactivate. The property _properties already contains the new properties.

destroyInstance(instance)

This method is called directly before deactivate if "instanceFactory" was set to true. If destroyInstance is not provided by the component instance, the SCR checks for a destroy or destroyRecursive method on the instance for self-destruction. (destroyRecursive supersedes destroy)

deactivate(componentContext)

Signals the start of the shutdown process of the component instance. All injected services are still available and the ejection phase starts after this method.

destroy() or destroyRecursive()

Signals the end of the life of the component instance. All services are ejected before this method, but _properties and _i18n are still accessible. The method destroyRecursive supersedes the destroy method if available, this rule is introduced for better Dijit widget support.

These methods are called in the life of a component in the order as listed.

Activation

  1. constructor: properties are handed over (if "propertiesConstructor" is true)

  2. init: is called before references are injected, _properties and _i18n are available

  3. set<ReferenceName>, add<ReferenceName>: SCR injects services

  4. activate: is called after references are injected

  5. createInstance: called if "instanceFactory" is true and must return the service instance

During Runtime (Component is activated)

  • modified: called if configuration was modified; _properties contains the new configuration properties.

  • set<ReferenceName>, add<ReferenceName>: SCR injects new registered services, if "policy": "dynamic".

  • unset<ReferenceName>, remove<ReferenceName>: SCR ejects unregistered services, if "policy": "dynamic"

Deactivation

  1. destroyInstance: called if "instanceFactory" is true and should clean up the service instance

  2. deactivate: is called before references are ejected from the component

  3. unset<ReferenceName>, remove<ReferenceName>: SCR ejects services

  4. destroy or destroyRecursive: is called after references have been removed, _properties and _i18n are still available

Configuration Reference

Following properties can be declared on a component description.

Property Sample Description

name

"name" : "MyService"

required

The name of the component. Used as "impl" value if "impl" is not declared. Must be unique inside a bundle.

impl

"impl": "MyService"

optional (default: value of name)

The implementation class. An instance of this class is created if the component has to create a new service.

enabled

"enabled": true

optional (default: true)

Specifies if the component should be enabled during the bundle start.

componentFactory

"componentFactory" : "FactoryName"

optional (default: "")

Specifies the name of the Factory Component, this name is available as service property "Component-Factory"

serviceFactory

"serviceFactory": "false"

optional (default: false)

Specifies if this component shall be newly created for each requesting bundle.

configPolicy

"configPolicy" : "optional"

optional (default: optional) Possible values are ignore or optional. The configPolicy required is not supported.

Specifies if a configuration element must be available at the Configuration Admin Service.

immediate

"immediate" : true

optional (default: false)

Specifies if this is an immediate component.

activate

"activate" : "activate"

optional (default: "activate"):

Specifies the method name to signal if the component is activated, it’s only called if present in the instance.

deactivate

"deactivate" : "deactivate"

optional (default: "deactivate")

Specifies the method name to signal if component is deactivated, it’s only called if present in the instance.

modified

"modified" : "modified"

optional (default: "modified")

Specifies the method name to signal if the component configuration was changed, it’s only called if present in the instance.

instanceFactory

"instanceFactory" :true

optional (default: false)

Marks that the component instance is a factory for a service instance. The component instance must provide a createInstance method if this flag is true.

createInstance

"createInstance" : "createInstance"

optional (default: "createInstance")

Specifies the method name to call for instance creation if this component is an instanceFactory.

destroyInstance

"destroyInstance" : "destroyInstance"

optional (default: "destroyInstance")

Specifies the method name to call for instance dispose if this component is an instanceFactory.

provides

"provides": [ "ct.service.api.log.LogService" ]

optional (default: [])

Declares all interface names for which this component is registered as service at the JS OSGi runtime. If this is not specified, this component is an immediate component.

propertiesConstructor

"propertiesConstructor" : false

optional (default: false):

If true, the declared properties are "injected" as constructor parameters.

properties

"properties":{
  "Event-Topics" : ["some/topic"],
  "_privateProperty" : "text"
}

optional (default: {})

Declares configuration properties and service properties. Properties starting with '_' underscore are treated as private and are only visible by this component. If "propertiesConstructor" is false, all properties are injected into a _properties member of the component instance.

references

"references":[{
 "name" : "tool",
 "providing" : "ct.tools.Tool"
}]

optional (default: [])

Array of reference declarations.

A reference declaration can have following properties.

Property Sample Description

name

"name":"log"

required

The internal name of this reference (used for injection and error logging).

providing

"providing":"ct.framework.api.LogService"

required

The interface name under which the target service must be registered in the service registry.

filter

"filter" : "(client=true)",

optional (default: "")

A filter expression which expresses further service metadata requirements of the reference. It is needed if the providing interface is not sufficient to find the correct target service.

policy

"policy" : "dynamic"

optional (default: "dynamic")

The binding style policy, possible values are "static" or "dynamic". The value "static" means that the component is restarted if the reference becomes unsatisfied but another matching reference is available. The value "dynamic" means that the component is able to handle the dynamic unbinding/binding and keeps running during reference updates.

cardinality

"cardinality":"1..1"

optional (default: "1..1")

Specifies the cardinality of the reference. Possible values are '0..1','1..1','0..n' and 1..n.

bind

"bind":"setLog",

optional (default: set<name>|add<name> depending on the cardinality)

Specifies the method called during the injection process. If the method is not present, the reference is injected as property "<name>" into the instance, in this sample "log".

unbind

"unbind":"unsetLog",

optional (default: unset<name>|remove<name> depending on the cardinality)

Specifies the method called during the ejection process.

noInjection

"noInjection" : false

optional (default: false)

Signals that this reference should not be injected and the component uses a programmatic resolving by applying the ComponentContext.locate* methods.

connect

"connect" : {
   "onClick" : "_handleClick"
}

optional (default: {})

Defines that the SCR should manage the connects to the reference. In the sample the SCR connects to the event onClick of the reference and route it to the _handleClick method of the instance.