Jump to: navigation, search

Using Blueprint

Blueprint is an OSGi compendium spec for a dependency injection framework designed specifically for use in an OSGi container. It was derived from Spring DM and is very similar. Karaf includes the Apache Aries blueprint implementation with its base features.

To use blueprint a bundle provides XML resource(s) that describe what OSGi service dependencies are needed, what Java objects to instantiate for the bundle's business logic and how to wire them together. In addition, a bundle can export/advertise its own OSGi services.

There are 4 main elements in a blueprint xml file: bean, service, reference and reference-list:

  • bean - an element that describes a Java object to be instantiated given a class name and optional constructor args and properties.
  • service - advertises a bean as an OSGi service
  • reference - imports a singleton OSGi service that implements a specified interface and/or satisfies a specified property filter
  • reference-list - imports multiple OSGi services that implement a specified interface and/or satisfy a specified property filter


For detailed documentation of these elements and blueprint design, refer to the Blueprint chapter of the OSGi compendium spec. Also refer to the Aries documentation

The blueprint extender is the component that extracts and parses blueprint XML resources from bundles as they are activated and creates the blueprint containers. By default, the extender looks for XML resources under the standard OSGI-INF/blueprint path inside bundles. The parsing and container creation is done asynchronously so there's no implicit deterministic startup ordering as is the case with Opendaylight's config subsystem via feature ordering. Therefore, in order to preserve this functionality with blueprint, if needed, and to avoid intermittent timing issues on startup, Opendaylight has its own component that scans a custom path, org/opendaylight/blueprint. This allows for the creation of blueprint containers to be potentially ordered. So it is recommended to put your blueprint XML files under src/main/resources/org/opendaylight/blueprint in your bundle projects (as of this writing, ordering hasn't been implemented as there hasn't been a need for it).

The following sections illustrate examples for writing blueprint XML for Opendaylight bundles along with some best practices and illustrate the Opendaylight's blueprint extensions that provide additional functionality and convenient shortcuts for using MD-SAL core services.

Foreword: XML vs. Java annotations

This guide describes "pure XML" Blueprint. Meanwhile, at least some ODL projects (notably e.g. Genius & NetVirt) have adopted Blueprint with (some) Java annotations instead of <bean> in XML. We recommend that you first read this page to get the basics, and if you're interested then consult the Best Practices DI Guidelines page to learn more about how to do this with Java annotation instead.

Importing MD-SAL services

The following XML imports the standard binding MD-SAL services, ie the DataBroker, RpcProviderRegistry, and NotificationPublishService, via reference elements.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
    odl:use-default-for-reference-types="true">

  <reference id="dataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker" odl:type="default"/>
  <reference id="rpcRegistry" interface="org.opendaylight.controller.sal.binding.api.RpcProviderRegistry"/>
  <reference id="notificationService" 
          interface="org.opendaylight.controller.md.sal.binding.api.NotificationPublishService"/>

</blueprint>

Internally, the reference element creates a proxy that implements the specified interface and wraps the actual service instance. This is done due to the dynamic nature of OSGi services as implementations may, theoretically, come and go if bundles are dynamically stopped or updated/restarted. In practice, though, this is difficult to achieve in large platforms like Opendaylight with hundreds of bundles (but that's another topic).

On startup, the container will first wait for all referenced service dependencies to be satisfied before continuing. The default timeout is 5 minutes. If the timeout expires, the container fails fast. In addition, if a service later becomes unavailable it will block method calls and wait the timeout period for a new service instance to become available. If the timeout expires, a runtime exception is thrown. This can be handled in application code if one really wants to support dynamic bundle updates but, as mentioned, in practice this is not realistically feasible with large applications.

There may be multiple service implementations advertised for the same interface. This is the case with the DataBroker - there is the default implementation and the specialized PingPongDataBroker. It is ambiguous as to which service is obtained unless an OSGi service property filter is specified to distinguish which implementation is desired.

MD-SAL services are advertised with a specific property named type. Opendaylight defines an extension attribute to the reference element named type that, internally, is added to the OSGi service filter. This is illustrated in the above example for the DataBroker reference element, where the odl:type attribute is set to default. If one wanted the PingPongDataBroker then set it to pingpong. In order to access the extension, the Opendaylight extension namespace, http://opendaylight.org/xmlns/blueprint/v1.0.0, must be specified in the root blueprint element and type must be prefixed appropriately.

As a convention, the default type is used for the default implementation of the service with other types being specialized implementations. This allows consumers to obtain whichever implementation the provider deems as the default without having to explicitly know it and allows the provider to switch to a new default implementation without requiring all consumers to change their referenced type.

To avoid having to specify the type attribute for every reference element, another Opendaylight extension, use-default-for-reference-types, can be specified in the root blueprint element. This attribute automatically adds a filter to all reference elements such that the type property is either not set or set to default. This ensures the default implementation is imported unless type is explicitly specified for a reference element, in which case it overrides the use-default-for-reference-types setting. It is recommended to always set use-default-for-reference-types to true as a fallback.

The odl:type attribute can also be specified for service elements as a shortcut to add the service property.

Instantiating business logic

Using direct class instantiation

Now that we've seen how to import MD-SAL services, the next step is to instantiate the business logic.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
    odl:use-default-for-reference-types="true">

  <reference id="dataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"/>

  <bean id="foo" class="org.opendaylight.app.Foo" init-method="init" destroy-method="close">
    <property name="dataBroker" ref="dataBroker"/>
  </bean>

  <bean id="bar" class="org.opendaylight.app.Bar">
    <argument ref="foo"/>
  </bean>

</blueprint>

Here we import the default DataBroker service, create an instance of class org.opendaylight.app.Foo and inject the DataBroker service via the property element by referencing its blueprint container id, dataBroker. The property element uses standard Java bean semantics where name must correspond to a void setter method on the Foo class with signature

public void setDataBroker(DataBroker dataBroker)

The optional init-method attribute specifies the name of a void no-arg method that the container will invoke after constructing the instance and setting the properties. Similarly, destroy-method specifies the method name to invoke when the container is being destroyed.

We also instantiate an instance of class org.opendaylight.app.Bar and inject the foo instance via a constructor argument.

This illustrates the 2 ways to inject dependencies, either via a property setter or a constructor arg. This is up to the class author but constructor args are usually preferred because the class member can be made final.

Using a static factory method

Sometimes a class author may use the pattern of defining a static method to create instances. For this example, the org.opendaylight.app.Foo class has a static method, newInstance:

public static Foo newInstance(DataBroker dataBroker) {
    ...
}

We can invoke the static newInstance method via the factory-method attribute and pass the dataBroker instance as the method argument:

<bean id="foo" class="org.opendaylight.app.Foo" factory-method="newInstance">
  <argument ref="dataBroker"/>
</bean>

The end result is an org.opendaylight.app.Foo instance identified in the container by id foo.

Using a Factory class

Another pattern is to use an instance Factory class to create instances of another class. For this example, there is an org.opendaylight.app.FooFactory class with a method to create org.opendaylight.app.Foo instances:

public class FooFactory {
    public Foo newInstance(DataBroker dataBroker) {
        ...
    }
}

We first create an instance of FooFactory and then define the foo bean to invoke the factory-method newInstance on the FooFactory instance referenced by factory-ref:

<bean id="fooFactory" class="org.opendaylight.app.FooFactory"/>

<bean id="foo" factory-ref="fooFactory" factory-method="newInstance">
  <argument ref="dataBroker"/>
</bean>

Advertising services

Typically Opendaylight applications only consume MD-SAL OSGi services but some may need to advertise their own. This is done via the service element.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <bean id="fooImpl" class="org.opendaylight.app.FooImpl">
    <!-- constructor args -->
  </bean>

  <service ref="fooImpl" interface="org.opendaylight.app.Foo" odl:type="default"/>

</blueprint>

Here we create an instance of org.opendaylight.app.FooImpl and advertise it with the implemented interface org.opendaylight.app.Foo. We also specify the Opendaylight type attribute extension as the default implementation. This is optional and only really necessary if there might be other implementations. Regardless it doesn't hurt to specify it.

MD-SAL blueprint extensions

Global RPCs

Opendaylight provides 2 blueprint extensions to make it easier to register and consume global MD-SAL RPC services.

To register an RpcService implementation, the rpc-implementation element is used. In the following example, assume org.opendaylight.app.FooRpcServiceImpl implements the generated binding org.opendaylight.app.FooRpcService interface.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <bean id="fooRpcService" class="org.opendaylight.app.FooRpcServiceImpl">
    <!-- constructor args -->
  </bean>

  <odl:rpc-implementation ref="fooRpcService"/>

</blueprint>

You simply reference the implementation bean instance and it automatically finds the implemented RpcService interface and registers the implementation with the MD-SAL RpcProviderRegistry. If the implementation implements multiple RpcService interfaces, they are all registered. A specific interface can also be specified via the interface attribute.

The registration is automatically closed when the container is destroyed.

To consume the org.opendaylight.app.FooRpcService, the rpc-service element is used.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <odl:rpc-service id="fooRpcService" interface="org.opendaylight.app.FooRpcService"/>

  <bean id="bar" class="org.opendaylight.app.Bar">
    <argument ref="fooRpcService"/>
  </bean>

</blueprint>

The rpc-service element obtains the RpcService implementation for the specified interface from the MD-SAL RpcProviderRegistry and creates a bean with id fooRpcService that is then injected into the org.opendaylight.app.Bar bean instance.

Routed RPCs

The routed-rpc-implementation extension element registers a routed RpcService implementation.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <bean id="fooRoutedRpcService" class="org.opendaylight.app.FooRoutedRpcServiceImpl">
    <!-- constructor args -->
  </bean>

  <odl:routed-rpc-implementation id="fooRoutedRpcServiceReg" ref="fooRoutedRpcService"/>

  <bean id="bar" class="org.opendaylight.app.Bar">
    <argument ref="fooRoutedRpcServiceReg"/>
  </bean>

</blueprint>

The routed-rpc-implementation element finds the RpcService interface implemented by the referenced bean and registers the implementation with the MD-SAL RpcProviderRegistry. It also creates a bean of type RoutedRpcRegistration with the specified id fooRoutedRpcServiceReg that is injected into the org.opendaylight.app.Bar bean instance. The RoutedRpcRegistration instance is automatically closed when the container is destroyed.

If the implementation implements multiple RpcService interfaces, it fails. In this case you must specify the desired interface via the interface attribute and create a routed-rpc-implementation element for each interface.

NotificationListener

The notification-listener extension element registers a NotificationListener implementation with the MD-SAL NotificationService to receive yang notifications.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <bean id="fooListener" class="org.opendaylight.app.FooNotificationListener">
    <!-- constructor args -->
  </bean>

  <odl:notification-listener ref="fooListener"/>

</blueprint>

Application configuration

Bundles may need some user configuration to initialize or augment the business logic. In OSGi, the common way to do this is via the ConfigAdmin service and .cfg files in the karaf etc directory. However using ConfigAdmin has limitations:

  • The configuration data is limited to key-value pairs although more complex representations are possible via embedded syntax but that can get ugly.
  • Data validation is minimal. You don't get the rich validation that yang provides.
  • The configuration can't be updated via restconf/netconf. One must log onto the system and edit the cfg file.
  • The configuration isn't clustered. In order to propagate changes to all nodes, one must edit the cfg file on each one.


The alternative is to store the configuration data in the MD-SAL datastore. This allows one to make use of the rich structures and validation that yang provides. This also has the advantage of distributing the configuration across the cluster.

For settings which must be per node instance, the ConfigAdmin is appropriate. Such settings are uncommon though. Otherwise it is recommended to use the datastore. Both methods are outlined in more detail below.

Using the Datastore

Opendaylight has a blueprint extension element, clustered-app-config, that makes it easy to access the application-defined configuration yang data. The yang data must be a top-level container or list element. A container is used for singleton configuration and a list would be used if you need multiple instances of a component each having its own configuration.

The clustered-app-config element obtains the app configuration data from the MD-SAL data store and makes it available as a bean. The app configuration is treated as a dependency in the same manner as OSGi service dependencies. If the configuration data cannot initially be obtained from the datastore, ie if the datastore isn't available for some reason, it will block the startup of the blueprint container until the datastore is available or if the container times out waiting.

The clustered-app-config element also internally registers a ClusteredDataTreeChangeListener that restarts the blueprint container when the configuration data is changed or deleted.

Using YANG container configuration

We define an example top-level yang container as follows:

module my-app-config {
    yang-version 1;
    namespace "urn:opendaylight:myapp:config";
    prefix my-app-config;

    description
      "Configuration for ...";

    revision "2016-06-24" {
        description
            "Initial revision.";
    }

    container my-config {
        leaf id {
            type string;
            mandatory true;
        }

        leaf connection-timeout {
            type uint16;
            default 3000;
        }
    }
}

In your blueprint XML, you specify the clustered-app-config element with the fully-qualified class name of the yang-generated top-level container interface via the binding-class attribute.

<odl:clustered-app-config id="myConfig"
      binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig">
</odl:clustered-app-config>

The clustered-app-config element obtains the data from the MD-SAL data store and makes the binding DataObject available as a bean that can be injected into other beans. Here we obtain the MyConfig container DataObject. If no DataObject instance exists, an instance is created and populated with any default values defined in the yang. In the above example, the MyConfig instance would be populated with the default value of 3000 for the connection-timeout leaf. The id leaf would be null.

In some cases you may not want to define a default value in the yang but you still want to provide a default or if you have a nested complex structure where you need to provide default data. This can be accomplished via the default-config child element.

<odl:clustered-app-config id="myConfig"
       binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig">
  <odl:default-config><![CDATA[
    <my-config xmlns="urn:opendaylight:myapp:config">
      <id>foo</id>
    </my-config>
  ]]></odl:default-config>
</odl:clustered-app-config>

Here we provide a default value for the id leaf. The default-config element must contain the XML representation of the yang data, including namespace, wrapped in a CDATA section to prevent the blueprint container from treating it as markup. This is the same XML you would specify if setting via RESTCONF.

The default data can also be specified in an external XML file. This is useful for scripting/test automation and convenience so one can change the configuration data without the controller running. The clustered-app-config will look for a file of the form <yang module name>_<container name>.xml in a well-known location, etc/opendaylight/datastore/initial/config. For example, in the above yang, the path and file name would be etc/opendaylight/datastore/initial/config/my-app-config_my-config.xml. The XML file name can also be explicitly specified via the default-config-file-name attribute of the clustered-app-config element.

Note: the default data is the initial data to use when the data hasn't been explicitly written/set in the data store. Once set in the data store, the default data is no longer used.

Putting it all together, we can then inject the MyConfig instance via its bean id, myConfig, into a business logic bean.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">

  <odl:clustered-app-config id="myConfig"
        binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig">
  </odl:clustered-app-config>
  
  <bean id="foo" class="org.opendaylight.myapp.Foo">
    <argument ref="myConfig"/>
  </bean>

</blueprint>

The constructor argument for the org.opendaylight.myapp.Foo class would be of type org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyConfig.

In some cases you may want to inject leaf values (or other child elements) directly into the business logic bean instead of the DataObject instance. For example, we define a org.opendaylight.myapp.Bar class with a constructor signature that takes the the id and connection-timeout.

public class Bar {
    public Bar(String id, int connectionTimeout) {
    }
}

We want to inject the id and connection-timeout leaf values from the MyConfig instance. This can be accomplished by defining nested beans for the constructor argument elements that extract and return the values from the MyConfig instance.

<bean id="foo" class="org.opendaylight.myapp.Bar">
  <argument>
    <bean factory-ref="myConfig" factory-method="getId" />
  </argument>
  <argument>
    <bean factory-ref="myConfig" factory-method="getConnectionTimeout" />
  </argument>
</bean>

The nested beans invoke the corresponding getters, specified by factory-method, on the MyConfig instance referenced via its bean id.

Using YANG list configuration

In cases where you need to instantiate multiple instances of a component, each having its own configuration, instead of creating a separate identical yang container model for each, you can use a top-level list element.

We define an example top-level list as follows:

module my-component-config {
    yang-version 1;
    namespace "urn:opendaylight:myapp:config";
    prefix my-component-config;

    description
      "Configuration for ...";

    revision "2016-06-24" {
        description
            "Initial revision.";
    }

    list my-component-config {
        key "instance-name";
        leaf instance-name {
            type string;
        }

        leaf connection-timeout {
            type uint16;
            default 3000;
        }
    }
}

The clustered-app-config element only supports one list key and it must be of type string. The key uniquely identifies an instance. The instance value must be specified via the list-key-value attribute.

<odl:clustered-app-config id="myComponentConfig1"
      binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyComponentConfig"
      list-key-value="instance1">
  <odl:default-config><![CDATA[
    <my-component-config xmlns="urn:opendaylight:myapp:config">
      <instance-name>instance1</instance-name>
      <connection-timeout>1000</connection-timeout>
    </my-component-config>
  ]]></odl:default-config>
</odl:clustered-app-config>

<bean id="foo1" class="org.opendaylight.myapp.Foo">
  <argument ref="myComponentConfig1"/>
</bean>

<odl:clustered-app-config id="myComponentConfig2"
      binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.myapp.config.rev160624.MyComponentConfig"
      list-key-value="instance2">
 <odl:default-config><![CDATA[
    <my-component-config xmlns="urn:opendaylight:myapp:config">
      <instance-name>instance2</instance-name>
      <connection-timeout>2000</connection-timeout>
    </my-component-config>
  ]]></odl:default-config>
</odl:clustered-app-config>

<bean id="foo2" class="org.opendaylight.myapp.Foo">
  <argument ref="myComponentConfig2"/>
</bean>

Here we define 2 instances of the configuration, each with a corresponding instance of org.opendaylight.myapp.Foo.

We also specify a separate default value for connection-timeout for each configuration. Since it's a list element, the instance-name key must be specified in the XML and it must correspond to the value specified via list-key-value.

Using ConfigAdmin

Using property-placeholder

Aries provides extensions to access .cfg settings. The simplest is via the stand-alone property-placeholder element as illustrated in the next example.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
                 xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
    odl:restart-dependents-on-updates="true">

  <cm:property-placeholder persistent-id="org.opendaylight.foo" update-strategy="none">
    <cm:default-properties>
      <cm:property name="max-retries" value="5"/>
    </cm:default-properties>
  </cm:property-placeholder>

  <bean id="foo" class="org.opendaylight.app.Foo">
    <property name="maxRetries" value="${max-retries}"/>
  </bean>

</blueprint>

To access property-placeholder, the Aries extension namespace, http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0, must be specified in the root blueprint element and property-placeholder must be prefixed appropriately.

The persistent-id attribute corresponds to the .cfg file under /etc but without the .cfg extension. You can optionally specify defaults for properties not specified in the .cfg file or if the .cfg file doesn't exist. Here we specify a default value for max-retries.

On container startup, the property-placeholder element accesses the .cfg file via the ConfigAdmin service and makes the properties available as variables that can be referenced in many elements. The variables are substituted with the actual property values. Here we inject the ${max-retries} value into the foo instance via the value attribute of the property element. The org.opendaylight.app.Foo class would have a corresponding setter with signature:

public void setMaxRetries(int retries) {
    ...
}

The property-placeholder element can dynamically respond to updates to the properties depending on the update-strategy setting. If set to none (the default), it does nothing. If set to reload, it will dynamically reload the blueprint container and re-create all the beans. The downside to this is that if the bundle advertises services, users of those services may experience disruption as the service instance is dynamically swapped out. To alleviate this, Opendaylight defines an extension, restart-dependents-on-updates, that performs an atomic and orderly restart of the container and all its dependent containers as determined by walking the service usage tree. So any bundle consuming any service provided by this container, either directly or indirectly, will also be restarted. The algorithm destroys containers bottom-up with this container last and restarts them top-down with this container first.

It is recommended to use restart-dependents-on-updates in lieu of the reload update-strategy even if the the bundle doesn't advertise any services in case service(s) are added later.

Note that restart-dependents-on-updates is an attribute of the top-level blueprint element. Ideally it should be defined for the property-placeholder element but, unfortunately, the xsd doesn't allow extensions.

Using managed-properties

The managed-properties element is another Aries extension that is attached to a specific bean and manages the configurable properties for that bean.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0">

  <bean id="foo" class="org.opendaylight.app.Foo">
    <cm:managed-properties persistent-id="org.opendaylight.app.foo"
			update-strategy="component-managed" update-method="update" />
  </bean>

</blueprint>

As with property-placeholder, the persistent-id attribute corresponds to the .cfg file. The component-managed update-strategy means that updates to the properties are essentially handled by the component, ie when the .cfg file is changed, the method specified via the update-method attribute is invoked with the Map of properties. The org.opendaylight.app.Foo class method must have this signature:

public void update(Map<String, String> properties) {
    ...
}

Note that component-managed doesn't call the update-method initially on container startup - only when the .cfg file is changed, added, or removed. In order to get the initial properties, you can combine it with the cm-properties element that provides a java.util.Properties instance, which is also a Map, that can be inject into the component-managed bean.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
                 xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0">

  <cm:cm-properties id="initialProperties" persistent-id="org.opendaylight.app.foo"/>

  <bean id="foo" class="org.opendaylight.app.Foo">
    <cm:managed-properties persistent-id="org.opendaylight.app.foo"
			update-strategy="component-managed" update-method="update" />
    <argument ref="initialProperties" />
  </bean>

</blueprint>

The org.opendaylight.app.Foo class constructor signature can take either a java.util.Properties or a Map<String, String>.

Note that the cm-properties id attribute requires schema version v1.2.0 not v1.1.0.

There is a another update-strategy called container-managed. With this strategy, the container invokes setter methods on the bean corresponding to the configuration property keys. update-method is not used with this strategy.

The container-managed strategy has the advantage of simpler code in that you let the container do most of the work but updates to the properties are not atomic. If atomicity isn't needed then container-managed is fine otherwise use component-managed.

We recommend that you set defaults in code, and keep the properties commented out. Advantages to this approach include that upgrades can have new defaults, if no customized, and that test or non-OSGi code will automatically use the same defaults from the code.

https://git.opendaylight.org/gerrit/#/c/67011/ is an example applying this.


Migrating from config subsytem (CSS)

This section describes how to migrate from using the CSS to blueprint and maintain backwards compatibility with respect to upgrades from prior releases.

First write the blueprint XML file for your application. For backwards compatibility, you must at least keep the config yang in case a config change was previously made via netconf or the restconf yang-ext mount in an existing installation. In this case, all the initial config XML files under etc/opendaylight/karaf are copied to a single file under etc/opendaylight/current and, on restarts, the CSS uses that file to push the configs, ie the files under etc/opendaylight/karaf are no longer used. Therefore, if the config yang was removed, the controller will fail on startup.

If your application doesn't provide any services via the CSS that can be injected into other applications/modules, then you essentially do nothing in the createInstance method of your Module class because all the code logic is wired via blueprint. For convenience, there's a NoopAutoCloseable instance you can return:

import org.opendaylight.controller.sal.common.util.NoopAutoCloseable;

public class MyAppModule extends ... {
    ...
    @Override
    public AutoCloseable createInstance() {
        // The application logic is created via blueprint and doesn't advertise any services so return a
        // no-op here for backwards compatibility.
        return NoopAutoCloseable.INSTANCE;
    }
}

You can remove the corresponding config XML file from your features.xml so it's no longer installed under etc/opendaylight/karaf.

If your application does provide a service via the CSS, then you must still return that service instance from the createInstance method of your Module class. Since the service is now advertised via blueprint, you merely obtain the service from the OSGi registry via its interface and return it. The blueprint container may not have been created yet, in which case you must do a blocking wait for the service to be advertised. You can use the WaitingServiceTracker class as illustrated below:

import org.opendaylight.controller.config.api.osgi.WaitingServiceTracker;
import org.osgi.framework.BundleContext;

public class MyAppModule extends ... {
    private BundleContext bundleContext;
    ...
    @Override
    public AutoCloseable createInstance() {
        // The service instance is created and advertised with the OSGi registry via blueprint
        // so obtain it here so we can return it to the config system. It's possible the blueprint container
        // hasn't been created yet so we busy wait 5 min for the service.
        final WaitingServiceTracker<MyService> tracker = WaitingServiceTracker.create(MyService.class, bundleContext);
        final MyService myService = tracker.waitForService(WaitingServiceTracker.FIVE_MINUTES);

        final class MyAutoCloseableService implements MyService, AutoCloseable {
            @Override
            public void close() {
                // We need to close the WaitingServiceTracker however we don't want to close the actual
                // service instance because its life-cycle is controlled via blueprint.
                tracker.close();
            }

            // delegate service interface methods to the actual myService instance
        }

        return new MyAutoCloseableService();
    }

    void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }
}

The WaitingServiceTracker#waitForService method waits 5 minutes for the actual service to be advertised which should be more than enough time. If, for some reason it times out, it will throw a ServiceNotFoundException. This should only happen if there's an error in the blueprint XML.

The WaitingServiceTracker maintains a reference to the actual service instance so it must be closed when the CSS module is closed so the blueprint container can be destroyed on shutdown. However, we don't want to close the actual service instance because it's lifecycle is controlled by blueprint and we don't want it closed prematurely if the CSS module is closed first. Therefore we create a local delegating class that implements the service interface and AutoCloseable. The close method is implemented to just close the WaitingServiceTracker instance with the rest of the methods delegated to the actual service instance.

The Module class above needs access to the BundleContext which can be injected by the corresponding ModuleFactory class as illustrated below:

import org.osgi.framework.BundleContext;

public class MyAppModuleFactory extends ... {
    @Override
    public MyAppModule instantiateModule(String instanceName, DependencyResolver dependencyResolver,
            MyAppModule oldModule, AutoCloseable oldInstance, BundleContext bundleContext) {
        MyAppModule module = super.instantiateModule(instanceName, dependencyResolver, oldModule,
                oldInstance, bundleContext);
        module.setBundleContext(bundleContext);
        return module;
    }

    @Override
    public MyAppModule instantiateModule(String instanceName, DependencyResolver dependencyResolver,
            BundleContext bundleContext) {
        MyAppModule module = super.instantiateModule(instanceName, dependencyResolver, bundleContext);
        module.setBundleContext(bundleContext);
        return module;
    }
}

Since other CSS modules depend on this module, the corresponding config XML file must remain and must continue to be installed by the features.xml until such a time when all dependent modules are converted to blueprint and backwards compatibility is no longer needed.