Using a ManagedServiceFactory

One of the most elusive, yet very useful parts of the OSGi specification is the ManagedServiceFactory (part of the ConfigurationAdmin specification, chapter 104 in the 4.2 OSGi compendium specification). A long time ago, I gave an example on the Apache Felix users mailing list, but it's hard to find. So, time for a nicer looking example. In the four steps below, we will take a simple program, and make a ManagedServiceFactory out of it. I find it useful to follow the progression, but if you're only interested in the ManagedServiceFactory part, skip to step 3. I assume you're familiar with basics of OSGi, and have seen a ManagedService up close before.

Running the examples

I have put up an example project, which shows all the code covered below. When you import the project, you will get a warning about a missing 'test' folder. Just create a new 'test' source folder, this is due to Zip not being able to package empty directories.

The examples use Neil Bartlett's excellent Bndtools, and next to the environment that ships with, we use

The case

Step one, code in package net.luminis.websitewatcher.withmain.

Suppose we want to have some service that checks the availability of our favorite website. It's pretty easy to devise a method that does this. Given that we have a method for this (full code in the example), we can call this repeatedly from a thread.

new Thread("Watcher for " + site.toExternalForm()) {
  public void run() {
    while (true) {
      if (isReachable(site)) {
        System.out.println(site.toExternalForm() + " is reachable.");
      }
      else {
        System.out.println(site.toExternalForm() + " is NOT reachable.");
      }
      try {
        Thread.sleep(5000);
      }
      catch (InterruptedException e) {
        return;
      }
    }
  }
}.start();

We can call this from a main method,

public static void main(String[] args) throws MalformedURLException {
 new WebsiteWatcher(new URL("http://www.google.com"));
}

Run this, and you will see whether or not Google is available from your machine, every five seconds.

Wrap it in a bundle

Step two, code in package net.luminis.websitewatcher.simplebundle and simplebundle.bnd.

We can take the code we had before, and wrap that in a bundle. We add an activator, which uses the Apache Felix Dependency Manager to ease or registration, and create a component for our watcher,

manager.add(createComponent()
  .setImplementation(new WebsiteWatcher(new URL("http://google.com"))));

In our WebsiteWatcher, we move the thread-code to an inner class, and add some plumbing code,

public WebsiteWatcher(final URL site) {
  m_watcher = new WatcherThread(site);
}

public void start() {
  m_watcher.start();
}

public void stop() {
  m_watcher.interrupt();
}

The start() and stop() methods are called by the Dependency Manager when our component is, well, started or stopped.

You can see this working for yourself by running the bnd.bnd file in the project.

A ManagedService

Step three, code in package net.luminis.websitewatcher.managedservice and managedservice.bnd.

Moving it up one notch, we create a ManagedService out of our component. To do so, we make the WebsiteWatcher implement ManagedService, and add some contants we will need later.

public class WebsiteWatcher implements ManagedService {
  public static final String PID = "net.luminis.websitewatcher.managedservice";
  public static final String URL = "url";

A ManagedService must implement an updated(...) method,

@Override
public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
  if (properties != null) {
    if (properties.get(URL) == null) {
      throw new ConfigurationException(URL, "url cannot be null");
    }
    try {
      m_site = new URL((String) properties.get(URL));
    }
    catch (MalformedURLException e) {
      throw new ConfigurationException(URL, properties.get(URL) + " is not a valid URL", e);
    }
  }
}

This takes some explanation. Line 3 is a nullcheck on the configuration we receive: null is a valid configuration if our configuration is being removed. However, we will let the Dependency Manager take care of wether or not our configuration is available, so we can ignore the null-case. Line 4 checks the availability of our configuration property, and on line 8 we create a new URL to watch from the URL property in the configuration. If any of these checks go wrong, we create a ConfigurationException telling the world what happened (lines 5, 11).

Finally, we need to instruct the Dependency Manager of our newfound managed-service-ness.

manager.add(createComponent() 
  .setImplementation(WebsiteWatcher.class)
  .add(createConfigurationDependency()
    .setPid(WebsiteWatcher.PID)));

What happens when we do this?


ManagedService
ManagedService

The Configuration Admin spec tells us that a ManagedService should be registered as a service, with the PID as a service property. When a configuration becomes available, the Configuration Admin will call that ManagedService with the configuration properties. The Dependency Manager nicely wraps this as a 'configuration dependency', i.e., our component can only start if it has a configuration.

In the example project, we use the Apache Felix FileInstall bundle to get configurations into the Configuration Admin. It watches the load directory for files with a name like <pid>.cfg. You can play around with the configuration in net.luminis.websitewatcher.managedservice.cfg, and see what happens!

 

A ManagedServiceFactory

Piece de resistance, code in package net.luminis.websitewatcher.managedservicefactory and managedservicefactory.bnd.

Having a configurable watcher for a single website is nice, but we really want to be able to watch more sites, without having to create PIDs for each of those, or instantiating multiple watchers 'by hand'. This is where the ManagedServiceFactory comes in.

ManagedServiceFactory
ManagedServiceFactory

The task of a ManagedServiceFactory is to create instances of whatever it manages, based on the configuration it gets.

We first create a ManagedServiceFactory implementation,

public class WebsiteWatcherFactory implements ManagedServiceFactory {
  public static final String PID = "net.luminis.websitewatcher.managedservicefactory";

  private volatile DependencyManager m_dependencyManager;

  private final Map<String, Component> m_components = new HashMap<String, Component>();

  @Override
  public String getName() {
    return "website watcher factory";
  }

  @Override
  public void updated(String pid, @SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
    if (m_components.containsKey(pid)) {
      return;
    }

    Component component = m_dependencyManager.createComponent()
      .setImplementation(WebsiteWatcher.class)
      .add(m_dependencyManager.createConfigurationDependency().setPid(pid));

    m_components.put(pid, component);
    m_dependencyManager.add(component);
  }

  @Override
  public void deleted(String pid) {
    m_dependencyManager.remove(m_components.remove(pid));
  }
}

Right, what happens here?

  • For each FactoryConfiguration, the Config Admin will call the updated() method on line 14. In here, we
    1. create a new Dependency Manager Component for our watcher (line 19),
    2. make it depend on a configuration for its own PID (line 21), like we did before,
    3. register its existence (line 23), and
    4. add the component to the Dependency Manager (line 24)
  • If a configuration is deleted, the deleted() method on line 28 will be called; we thus remove the related component from the Dependency Manager.

Note the private volatile DependencyManager field: the Dependency Manager will inject an configured instance in each component that has a field of this type.

Now we have a factory, we need to register it in our activator.

Properties props = new Properties();
props.put(Constants.SERVICE_PID, WebsiteWatcherFactory.PID);

manager.add(createComponent()
  .setInterface(ManagedServiceFactory.class.getName(), props)
  .setImplementation(WebsiteWatcherFactory.class));

And with that, we have a fully functional factory for creating numerous watchers of all you favorite websites. Apache Felix FileInstall will also handle factory configurations if you use filenames like <factoryPid>-<instance>.cfg, like net.luminis.websitewatcher.managedservicefactory-google.cfg.

When creating configurations, you might get a warning like

*ERROR* Configuration for net.luminis.websitewatcher.managedservicefactory.560fe0c0-a691-475e-854a-b4caab68f6d4 has already been used for service [org.osgi.service.cm.ManagedServiceFactory, id=39, bundle=10] and will now also be given to [org.osgi.service.cm.ManagedService, id=41, bundle=10] which you can safely ignore. This has to do with the fact that the configuration is initially linked to our factory, but is used to configure the generated service later on.

By the way, you may have noticed that we wrote a new WebsiteWatcher for each step of the process, except for the last one: we just reused the one from step 3.