Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development • Apache integration software

Dynamic configuration of OSGi bundles running on Apache Karaf, from the ground up

This article extends my previous article Creating OSGI bundles and services from the ground up using Apache Karaf, and explains how dynamic configuration is supposed to work in the OSGi world. By 'dynamic' I mean that configuration information is passed to the application (the OSGi bundles provided by the developer) by the OSGi container, not only when the bundles are started, but whenever an administrator changes a configuration setting. Since OSGi bundles frequently provide long-lived, server functionality, some way of changing configuration without restarting the entire application is often essential.

About this example

In the previous example, two OSGi bundles, Tick and Tock, interacted in a service producer/consumer relationship. Everytime Tick ticked, tock received an notification. Tick was hard-coded to wake up every five seconds. In this example, the tick interval will be controlled by a configuration paramter that can be adjusted by the administrator whilst the bundles are running.

Note:
This article will make absolutely no sense unless you have followed the previous one, or you already know in outline how OSGi works. Even in the latter case, I would advise reading the previous article, as this one uses essentially the same code, structured into the same directories, and built using largely the same commands.

The other significant difference between this article and the previous one is that in this case we'll be using the log4j logging API to write diagnostic messages, rather than just dumping them to the Karaf console. There are two reasons for this. First, in order to demonstrate that configuration can be changed at runtime, we'll need to execute console commands whilst the bundle is running, and that is rather difficult when there is a constant stream of messages on the console. Log messages written using log4j go, by default, to the Karaf log file (data/log/karaf.log). It will be necessary to make a small modification to the bundle's manifest file to enable the use of log4j at runtime (more on this point later.) Second, I want to use the same bundles in a later article to demonstrate how to deploy an application as a profile in a distributed environment using Apache Fabric8. In this case, the JVM hosting the application will not be the same as the one hosting the console, so we need to do something more sophisticated with logging.

As always, this example uses only a text editor and command-line build tools, in addition to Apache Karaf. I assume that Karaf is installed and running, as described in the previous article. Source code for this article is available from the Download section at the end of the article. I have tested the code and instructions using Karaf 2.3.3 on Fedora Linux.

Dynamic configuration principles

Like just about everything else in the OSGi world, dynamic configuration is effected by bundles interacting via services. In this case, our bundle registers itself with the management service, which is implemented in another bundle. Our bundle passes a reference to itself to the management service, and this service calls the bundle whenever the administrator makes changes to the bundle's configuration.

It is important to understand that all the OSGi container provides is a method for passing the configuration data at runtime: it does not assist the bundle in processing it. If the changes require, for example, a running thread to be destroyed and a new one created with different settings, then the bundle must take care of that. Changing the configuration won't restart the bundle or reload any dependent classes. In addition, the bundle must be prepared to work without being configured; that is, it must provide its own useable defaults. Some OSGi containers will start a bundle before it is configured, so the bundle must handle that situation gracefully.

Configuration settings are provided in the form of simple name-value pairs. The container groups these under a "Persistent ID" (or PID, which rather unhelpfully is more commonly used to abbreviate Process ID). The PID identifies a group of configuration settings — in principle a bundle can register for more than one PID. This value of a PID is arbitrary, but clearly each PID must to be unique in the container, so some sort of naming control is required. In a simple case, the PID can be the same as the name of the service, or one of its classes.

Building, deploying, and starting the improved Tick service

Coding modifications

To recap, the example consists of two OSGi bundles called Tick and Tock. The Tick bundle runs as a service, and notifies any other bundle that has expressed an interest at fixed time intervals. The Tock bundle registers and interest in the Tick service, and gets notified on each tick. Both bundles write their progress to the log using log4j API calls.

SInce the code changes are relatively minor, I'll only describe what's different about the new version. Note that the complete source code for this example, structured so as to make the commands in the article work, is available from the Download section at the end of the article.

The activator class for the Tick service now implements the update() method from the ManagedService API. It also imports the log4j APIs which will be needed for logging, and instantiates a static Logger class, as is usual with log4j.

import org.osgi.service.cm.*;
import org.apache.log4j.*;
//...
public class Activator implements BundleActivator, TickService, ManagedService
  {
  static Logger logger = Logger.getLogger (Activator.class.getName());

  //...
  }
The log() method simply calls logger.info(). All calls to System.out.prinln in the previous article have been changed to calls to log().
  public void log (String s)
    {
    logger.info (s);
    }
Rather than hard-coding a tick delay of 5000 msec, we define a variable and a default value:
  // Time to delay between ticks
  int delay = DEFAULT_DELAY;

  // Default delay, in case none is specified
  static int DEFAULT_DELAY = 5000;
We also need an OSGi "Persistent ID" to identify this bundle to the framework for configuration purposes.
  static String CONFIG_PID = "net.kevinboone.tick";
All the substantial work takes place in the updated() method. This is passed a Dictionary object containing the name-value pairs of the configuration. Note that if no configuration has (yet) been defined by the administrator, then a null dictionary object is passed to the method; this is our signal to install the default configuration. If the administrator has defined some configuration for this bundle, but not the value of the attribute we need, the dictionary object will be non-null, but the value of the configuration entry we need will be null. This also is a situation that the method needs to deal with.
  public void updated (Dictionary config)
    {
    log ("updated() called");
    if (config != null)
      {
      log ("Passed a non-null configuration");
      String newDelay = (String)config.get("delay");
      if (newDelay != null)
        {
        log ("Passed a new delay: " + newDelay + " msec");
        delay = Integer.parseInt (newDelay);
        }
      else
        {
        log ("Passed a null delyy");
        delay = DEFAULT_DELAY;
        }
      }
    else
      {
      log ("Passed a null configuration");
      delay = DEFAULT_DELAY;
      }
    }
To enable dynamic configuration, the bundle must register an interest in the management service, providing the name of its PID (Persistent ID):
    Hashtable <String, Object> properties = new Hashtable<String, Object>();
    properties.put(Constants.SERVICE_PID, CONFIG_PID);
    bundleContext.registerService (ManagedService.class.getName(),
     this , properties);
In the bundle manifest file MANIFEST.MF, we must declare that we import log4j, and also the bundle that provides the ManagerService APIs. As a reminder, omitting these settings will not prevent the bundle from building, but it won't start, because the container will only make available classes that the bundle explicitly declares that it needs.
Import-Package: org.osgi.framework, org.osgi.service.cm, org.apache.log4j

Building the bundles

Building the bundles is the same as in the previous article, except that we need to add two things to the class search path: the Apache log4j API JAR, and the JAR containing the implementation of the ManagedService classes. This latter might be a bit awkward to find; the JAR is called org.apache.felix.configadmin-X.X.X.jar, and you might have to hunt in the Karaf installation to find it. In version 2.3.3 of Karaf, the location is system/org/apache/felix/org.apache.felix.configadmin/1.6.0, and the filename org.apache.felix.configadmin-1.6.0.jar.

So the javac commands you'll need to build the code are:

$ javac -d target/tick/classes -classpath /path/to/karaf.jar:\
  /path/to/log4j-1.2.17.jar:\
  /path/to/org.apache.felix.configadmin-X.X.X.jar \
  src/net/kevinboone/osgitest/tick/*.java 

$ javac -d target/tick/classes -classpath /path/to/karaf.jar:\
  /path/to/log4j-1.2.17.jar:\
  /path/to/org.apache.felix.configadmin-X.X.X.jar \
  src/net/kevinboone/osgitest/tick/*.java
As always, these commands need to be entered as one line and, of course, you'll need to change the paths so that they are appropriate for your installation.

Building the bundles into JAR files is the same as in the previous article:

$ jar cfm osgitest_tick2.jar etc/MANIFEST_tick.MF  -C target/tick/classes . 
$ jar cfm osgitest_tock2.jar etc/MANIFEST_tock.MF  -C target/tick/classes . 
Note that I've changed the names of the JARs from the previous example, just adding a '2' on the end of the name; there is no functional reason for that, it just stops me getting the examples mixed up.

Installing the bundles

I'll only describe the installation of the Tick bundle, because that is all that is necessary to describe the dynamic configuration features. The Tock bundle is installed in exactly the same way if necessary, but it has no configuration, dynamic or otherwise.

To install and start the bundle, use osgi:install and osgi:start in the Karaf console:

karaf@root> osgi:install file:///path/to/osgitest_tick2.jar
Bundle ID 267
karaf@root> osgi:start 267
Note that the bundle number will almost certainly not be 267, as in this example, as these numbers are allocated by the container.

When the bundle starts, you should see the following output in the karaf.log file:

267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Tick bundle started
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | updated() called
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Passed a null configuration
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Tick! (v2.05) after delay 5000 msec
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Tick! (v2.05) after delay 5000 msec
...
Since no configuration has been defined, the updated() method is called with a null configuration, so the bundle must install its default delay — 5000 msec in this case. So we should see the Tick! message in the log every five seconds.

Changing the configuration

Now the bundle is running, we can change the configuration, and (hopefully) see the changes take effect immediately. The following Karaf console commands will change the value of the delay property. Note that the bundle is not notified until config:update is called; this allows multiple changes to be applied in a single call to the bundle's update() method.
karaf@root> config:edit net.kevinboone.tick
karaf@root> config:propset delay 10000
karaf@root> config:update
You should see the following output in the karaf.log file:
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | updated() called
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Passed a non-null configuration
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Passed a new delay: 10000 msec
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Tick! (v2.05) after delay 10000 msec
267 - net.kevinboone.OSGiTest.tick - 1.0.2 | Tick! (v2.05) after delay 10000 msec
The rate of ticks should slow immediately to one every ten seconds. It should not be necessary to restart the bundle or the container.

Summary

This article has explained how to implement dynamic configuration in an OSGi bundle, and how to set that configuration using Karaf console commands. Some OSGi containers provide nice graphical or web-based consoles for configuration, but the implementation of the bundle should be broadly the same.

In a later article, I'll describe how the Tick/Tock bundles can be grouped as a 'feature' in Apache Fabric8 (or JBoss Fuse), and assigned to multiple containers that are managed at runtime.

Downloads

Source code bundle

Copyright © 1994-2013 Kevin Boone. Updated May 01 2014