OSGi Getting Started

By Michael N. Lipp

Mastodon Follow

View GitHub Project

Introduction

Part 1: The container

Part 2: Simplest Bundle

Part 3: Simple Bundle

Part 4: Eclipse (OSGi) Plugin

Part 5: Bndtools

Part 6: Shift in Perspective

Part 7: Modules and Services

Part 8: Accessing a Service

Part 9: Tracking a Service

Part 10: Using a Service

Part 11: Providing a Service

Interlude: Cleaning up

Part 12: Configuration Admin

Part 13: Service Components

Part 14: Repositories

Part 15: Versions

From Services to Service Components

As we have seen in the previous parts, the service registry provides a very simple and flexible mechanism to establish the relationships between the modules of a software system (provided as bundles). The drawback of its simplicity is that it requires a relatively large effort for individual modules to manage their service dependencies.

A somewhat different approach to managing services is the use of “service components”. The basic ideas have been described in the article “Automating Service Dependency Management in a Service-Oriented Component Model”. Read it now. It’s a very good introduction into the topic1.

Service components supply the information required for managing the service dependencies. The management functions that evaluate this information are implemented in an independent component (the “dependency manager” or “service component runtime”) that is deployed in an OSGi framework in addition to the service components. All dependency managers work according to the same basic pattern. They watch for bundle state changes, either by monitoring the framework or by requiring a bundle to notify the manager about its start explicitly in its bundle activator. Once the bundle has been started, the dependency information is retrieved. When all the required (i.e. non-optional) services are available, some startup-method of the service component is invoked. (And, of course, there is a shutdown-method, which is invoked if one of the required services goes away.)

Felix Dependency Manager (DM)

One of the oldest and at the same time still recent solutions to dependency management is the Felix Dependency Manager (DM). It has its origins in 2004, but has undergone a major overhaul in 2019. The DM supports two mechanism for supplying the dependency information: programmatically or by using annotations. This provides a good entry point into the topic and that’s why I’ll show you the basics in the next sub-sections, although “OSGi Declarative Services” (see below) is the predominantly used solution.

Using DM programmatically

When using Felix DM programmatically, you have to provide a bundle activator that extends the abstract class DependencyActivatorBase and implements the init-method. This method tells the manager about the service requirements (and about provides services) using the dependency manager’s API.

public class Activator extends DependencyActivatorBase {
    
    public void init(BundleContext context, DependencyManager manager)
            throws Exception {
        manager.add(
            createComponent() // Create a new service component as instance...
            .setImplementation(HelloWorld.class) // ... of the HelloWorld class.
            .add(             // Add to the service component ...
                createServiceDependency() // ... a dependency on ...
                .setService(LogService.class) // ... the LogService service ...
                .setRequired(true) // ... but don't start the instance
                                   // before the LogService is available.
            )
        );
    }
}

The adapted version of your component class looks like this:

public class HelloWorld extends Thread {

    private volatile LogService logService;
        
    @Override
    public void run() {
        System.out.println("Hello World!");
        while (!isInterrupted()) {
            try {
                logService.log(LogService.LOG_INFO, "Hello Word sleeping");
                sleep (5000);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
    
}

To get this working, we have to add the DM to the sets of “build” and “run” bundles. During runtime, the DM has additional required dependencies on the OSGi “Configuration Admin” and the “Metatype” services. So we have to add those as well. You can do this using the GUI (see below), of course. What you end up with are entries in the bnd.bnd similar to this:

-buildpath: \
        osgi.cmpn;version=4.3.1,\
        osgi.core;version=4.3.1,\
        org.apache.felix.dependencymanager;version=4.4.0
-runbundles: \
        org.apache.felix.log,\
        org.apache.felix.gogo.command,\
        org.apache.felix.gogo.runtime,\
        org.apache.felix.gogo.shell,\
        de.mnl.osgi.log.fwd2jul,\
        org.apache.felix.dependencymanager,\
        org.apache.felix.configadmin,\
        org.apache.felix.metatype

Run the modified bundle with the enhanced configuration and – it works, i.e. we get the “Hello World!” and the log messages. This is nice to see, but where’s the magic, I mean, what started our thread? As it happens, the methods invoked by the DM on a service component are

Except for the first, these methods are all defined by the Thread class, which our component inherits from, and this is why our component works. Of course, since the stop and destroy methods are deprecated (and for good reasons), we should override them with proper implementations for anything beyond this simple example2.

The log service has been made available by the DM in the attribute logService by dependency injection. The details can all be found in the DM documentation.

When it comes to optional services, DM supports a nifty feature known as “Null Object”. Try it out: change the parameter of setRequired in the activator’s init-method to false and remove the log service bundle from the set of run bundles. Our component continues to work, despite the fact that we should get a NullPointerException when trying to invoke the log-method (logService.log(...)), since there cannot possibly be a log service available. If an optional dependency is not available, DM injects an instance of class Proxy that simply handles all method invocations by doing nothing (except for returning a “zero” value matching the method’s return type). Of course, it’s not guaranteed that this “dummy service implementation” won’t cause trouble in the invoking code3. But (as in the case of the log service) this feature can sometimes simplify the code because you don’t have to check whether an optional service is actually available.

Using DM with annotations

An easier way to specify service related information is the use of annotations. You can do this in the component class like this:

package io.github.mnl.osgiGettingStarted.simpleBundle;

import org.apache.felix.dm.annotation.api.Component;
import org.apache.felix.dm.annotation.api.ServiceDependency;
import org.apache.felix.dm.annotation.api.Start;
import org.apache.felix.dm.annotation.api.Stop;
import org.osgi.service.log.LogService;

@Component(provides={})
public class HelloWorld implements Runnable {

    @ServiceDependency(required=true)
    private volatile LogService logService;

    private Thread runner;
    
    @Start
    public void start() {
        runner = new Thread(this);
        runner.start();
    }
    
    @Stop
    public void stop() {
        runner.interrupt();
        try {
            runner.join();
        } catch (InterruptedException e) {
            logService.log(LogService.LOG_WARNING, 
                    "Could not terminate thread properly", e);
        }
    }
    
    @Override
    public void run() {
        System.out.println("Hello World!");
        while (!runner.isInterrupted()) {
            try {
                logService.log(LogService.LOG_INFO, "Hello Word sleeping");
                Thread.sleep (5000);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

}

Note that we don’t need an activator any more. The annotations specify the same information as the API calls in the previous example and should be self-explanatory4, maybe with the exception of the @Component annotation’s parameter “provides”. All interfaces directly implemented by a class annotated as @Component are by default assumed to be interfaces of services provided by the component. (Using the API, declaring a component as provider of a service would require an extra call.) Of course, we don’t want our component to be registered as a provider of the java.lang.Runnable “service”. Therefore we have to enumerate the provided services (none) explicitly.

In order to make the annotations known to the java compiler, you have to add org.apache.felix.dependencymanager.annotation-x.y.z.jar to the build path (as you did before with org.apache.felix.dependencymanager, see above). In an additional build step, the annotations are then used to create a file META-INF/dependencymanager/io.github.mnl.osgiGettingStarted.simpleBundle.HelloWorld in the bundle during packaging. Using the code above, the generated file looks like this:

{"impl":"io.github.mnl.osgiGettingStarted.simpleBundle.HelloWorld",
 "stop":"stop",
 "start":"start",
 "type":"Component"}
{"service":"org.osgi.service.log.LogService",
 "autoConfig":"logService",
 "type":"ServiceDependency",
 "required":"true"}

There are two ways to add the additional build step for creating this file to the bnd packaging tool. If you want to use the dependency manager in a single project only, you can add the lines

-pluginpath:\
	${workspace}/cnf/cache/org.apache.dependencymanager.annotation-5.0.0.jar;\
	url=http://repo1.maven.org/maven2/org/apache/felix/org.apache.felix.dependencymanager.annotation/4.2.0/org.apache.felix.dependencymanager.annotation-5.0.0.jar	
-plugin: org.apache.felix.dm.annotation.plugin.bnd.AnnotationPlugin;log=debug

to the project’s bnd.bnd. If you want to enable support for the whole (bnd-)workspace, add the lines to the build.bnd in the cnf project. The first line tells bnd about the location of the plugin and the second instructs it to apply the plugin.

If you’re impatient and run the compiled bundle now, nothing’s going to happen. As there is no activator any more, another mechanism must notice if a bundle with files META-INF/dependencymanager/* in it is started, read the information from the files and act accordingly. Such a mechanism is provided by the dependency manager as bundle org.apache.felix.dependencymanager.runtime-x.y.z.jar. Add it to the run bundles and start it again.

OSGi Declarative Services

An important sentence in the article “Automating Service Dependency Management in a Service-Oriented Component Model” mentioned at the beginning is: The prototype platform for our research is implemented using the Open Services Gateway Initiative (OSGi) […] framework, but the ideas are general enough for use in other service platforms. This basically still holds true after the adoption of the concepts outlined in the article as an OSGi specification.

The OSGi alliance has added support for service components as “Declarative Services”, initially in Release 4 of the service specification (2005)5. “Declarative Services” as specified introduce a new layer of abstraction, which implies that you don’t need to know about some parts of the Core OSGi APIs any more in order to use OSGi as a service based component framework. Some OSGi enthusiasts therefore consider “Declarative Services” to be a kind of “fresh start” for OSGi and tell you to more or less forget about the service layer (API). I could follow this line of thought easily, if they had made “Declarative Services” the core of an “OSGi NG” and had dropped the no longer needed low level APIs6. As it is, I find that you still have to understand the basics in order to fully understand the (debug) outputs and solve some problems that you may encounter7.

As the name “Declarative Services” suggests, there is no API as in Felix DM. Rather, services are declared using a header in MANIFEST.MF, e.g.:

Service-Component: OSGI-INF/io.github.mnl.osgiGettingStarted.simpleBundle.HelloWorld.xml

The file name pattern “OSGI-INF/<fully qualified class name>.xml” isn’t mandatory but recommended.

If we want our component to be a declared service component, the file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
  name="io.github.mnl.osgiGettingStarted.simpleBundle.HelloWorld" 
  activate="start" deactivate="stop">
  <implementation class="io.github.mnl.osgiGettingStarted.simpleBundle.HelloWorld"/>
  <reference name="LogService" interface="org.osgi.service.log.LogService" 
    bind="setLogService" unbind="unsetLogService"/>
</scr:component>

You would probably not be able to write this file from scratch without further guidance, but when you read it, it’s quite self-explanatory. The only thing that might be irritating is the reference to the log service, especially the bind and unbind attributes. Well, some things have to be approached a bit differently compared to Felix DM. Actually, I didn’t write the file shown above. I used the annotations that have been added in release 4.3 of the specification and let bnd generate it8. The source code should clarify what happens with the reference to the log service (the complete project is available here).

package io.github.mnl.osgiGettingStarted.simpleBundle;

import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.log.LogService;

@Component(service={})
public class HelloWorld implements Runnable {

    private static LogService logService;

    private Thread runner;
    
    @Reference
    private void setLogService(LogService logService) {
        this.logService = logService;
    }

    private void unsetLogService(LogService logService) {
        this.logService = null;
    }

    @Activate
    public void start(ComponentContext ctx) {
        runner = new Thread(this);
        runner.start();
    }
    
    @Deactivate
    public void stop(ComponentContext ctx) {
        runner.interrupt();
        try {
            runner.join();
        } catch (InterruptedException e) {
            logService.log(LogService.LOG_WARNING, 
                    "Could not terminate thread properly", e);
        }
    }

    @Override
    public void run() {
        System.out.println("Hello World!");
        while (!runner.isInterrupted()) {
            try {
                logService.log(LogService.LOG_INFO, "Hello Word sleeping");
                Thread.sleep (5000);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

}

The @Component annotation for the class looks similar the one in the Felix DM example. The parameter “service” serves the same purpose as the parameter “provides” of the DM annotation: it prevents our component from being registered as provider for service java.lang.Runnable.

Declarative services supports setting the values of fields starting with version 1.3 (OSGi version 6). Before this, referenced services are made known to the component by invoking a setter method. Considering our example, that’s not too bad because we can make the field with the reference to the log service static again9. In order to avoid keeping a reference to a log service implementation even if it disappears (and our component is stopped), we have to provide an “unset” method as well10. It doesn’t need an annotation. Rather, if there is a “setXYZ” method with the @Reference annotation, declarative services automatically assume an “unsetXYZ” method to implement the corresponding “undo” operation.

As with Felix DM, you need a “Service Component Runtime” (as the OSGi specification calls it) to start up the service components. The SCR looks for Service-Component headers in all deployed bundles and creates service components, registers their services and activates the components according to the directives in the XML file. Of course, you can use any implementation. But in our environment, the easiest way is to add the bundle org.apache.felix.scr to the run bundles.

Since Felix SCR has a dependency on an OSGi service called “Configuration Admin”, we actually have to add two bundles to the configuration, which results in a bnd.bnd that looks like this:

-buildpath: \
	osgi.cmpn;version=4.3.1,\
	osgi.core;version=4.3.1,\
	osgi.annotation
-runbundles: \
	org.apache.felix.log,\
	org.apache.felix.gogo.command,\
	org.apache.felix.gogo.runtime,\
	org.apache.felix.gogo.shell,\
	de.mnl.osgi.log.fwd2jul,\
	org.apache.felix.configadmin,\
	org.apache.felix.scr

Defining a Declarative Service

Compared to using the OSGi Core API, Declarative Services makes providing a service a lot easier. A provider for the calculator service from chapter Providing a Service can simply be made available by annotating the implementation class as Component. We don’t need an activator any more (you can find the complete project here).

package io.github.mnl.osgiGettingStarted.calculator.ds;

import org.osgi.service.component.annotations.Component;

import io.github.mnl.osgiGettingStarted.calculator.Calculator;

@Component(property = Constants.SERVICE_VENDOR + "=Michael N. Lipp")
public class CalculatorImpl implements Calculator {

	@Override
	public double add(double a, double b) {
		return a + b;
	}

}

The Component annotation has a lot of optional elements that can be used to further specify the properties of the service that is provided.

Properties

A property element can be used to specify properties that are added when the service is registered (as explained in “Providing a Service”).

Service Scopes

Similar to registering services using the OSGi core API, you can specify a scope for the service provided by a component. The default scope is SINGLETON (with BUNDLE or PROTOTYPE as alternatives).

Delayed Services

By default, the SCR delays the activation of a service (and thus the creation of the service component) until the service is requested. This behavior can speed up the startup time considerably.

If you want a service component to be created and activated immediately on startup, you have to set the immediate element of the component annotation to true.

Factory Component

Usually, a single component instance is created (and any provided service is registered) when all referenced services have become available. Sometimes, however, you may want to create one or more instances of a component (and register its provided services) dynamically at runtime.

This is made possible by specifying a factory attribute with an identifier. If defined, SCR registers a (factory) service with this identifier that provides a single method newInstance. This service can then be used to dynamically create component instances.

Note that this approach is different from creating components using a ManagedServiceFactory as presented in “Configuration Admin”. More on the differences will be explained in the next section.

Integration with Configuration Admin

The SCR is closely coupled with Configuration Admin. Every component has a configuration PID. If not specified explicitly, it defaults to the component’s name which in turn defaults to the name of the implementation class.

Configuring components

By default, values stored in Configuration Admin are merged with the values specified in the component annotation, with the former taking precedence over the latter. To demonstrate this behavior, run the project that implements the calculator service using Declarative Services and execute the following commands in the GoGo shell (output partially shortened or omitted):

g! serviceReference io.github.mnl.osgiGettingStarted.calculator.Calculator
Properties           [service.vendor=Michael N. Lipp, component.id=0, component.name=io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl, ...

g! conf = cm:getConfiguration io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl "?"
g! $conf update (new java.util.Hashtable ["service.vendor"="Nobody"])
g! serviceReference io.github.mnl.osgiGettingStarted.calculator.Calculator
Properties           [service.vendor=Nobody, ...]
...

If the log level is set to “information”, you can observe that updating the properties in Configuration Admin causes SCR to unregister the service and re-register it. This means that changing properties this way may be a costly operation.

Creating components

How the information from Configuration Admin is handled by SCR can be configured for each component individually by specifying a configurationPolicy element. The default value is OPTIONAL which results in the behavior demonstrated above.

An interesting alternative is the value REQUIRE. Add configurationPolicy = ConfigurationPolicy.REQUIRE to the parameters of the @Component annotation and restart the framework. Try to find our calculator service in the GoGo shell.

g! serviceReference io.github.mnl.osgiGettingStarted.calculator.Calculator

It’s no longer there. REQUIRED means that SCR won’t create an instance of the component unless there is a configuration with the component’s PID. Try:

g! conf = cm:getConfiguration io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl "?"
g! $conf update null
g! serviceReference io.github.mnl.osgiGettingStarted.calculator.Calculator
Properties           [service.vendor=Michael N. Lipp, component.id=0, ...]
...

… and the service is back again. Effectively this means that a component won’t be created with its default properties. It will only be created when it has been configured explicitly. Of course, if you make sure that the configuration isn’t deleted, the configuration and thus the service will be immediately available after a restart.

Maybe one of the most curious characteristics of this policy is that the component’s PID can – without any further change – also be used as a factory PID in Configuration Admin. Let’s create two instances of the calculator:

g! conf = cm:getFactoryConfiguration io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl Test1 "?"
g! $conf update null
g! conf = cm:getFactoryConfiguration io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl Test2 "?"
g! $conf update null
g! getAllServiceReferences io.github.mnl.osgiGettingStarted.calculator.Calculator null
000032  12 Calculator    io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl~Test2
000031  12 Calculator    io.github.mnl.osgiGettingStarted.calculator.ds.CalculatorImpl~Test1

Of course, a real use case will pass different sets of properties when calling the update method, thus configuring different instances of a service (e. g. two instances of a network client each connecting to a different server).

There are thus two types of dynamically created components. Components created by invoking the newInstance method of a factory service provided by SCR for a component factory (as described in the previous section), and components created by providing a factory configuration. The former are transient, the latter persist across restarts11.


  1. Note that this is the point where the term “component” is formally introduced in OSGi. I have sometimes referred to the HelloWorld class as a component, but up to now, this has been my conceptual view on this class, nothing formalized. 

  2. Unfortunately, stop is final and cannot be overridden. I’ll improve the solution in the second part. 

  3. It can be outright dangerous. Imagine that the calculator service from “Providing a service” is missing and all invocations of add simply return 0 as result. 

  4. The “required” element included in the @ServiceDependency cannot be found in the “manual” pages, which obviously aren’t complete. When in doubt, have a look at the javadoc

  5. I recommend to first have a look at the initial version of the specification. It is easier to read because it focuses on the core functions. And it uses event based explanations of the internal workings, which is close to the specification of the service layer. Starting with version 7, the specifications use the more abstract concept of dependency injection. 

  6. And IMHO if they had also switched to an easier to read style for the specifications, this might have been a successful new beginning for OSGi as well. 

  7. Not knowing about the underlying mechanisms is a bit like teaching programmers nothing about memory management because “Java does it automatically”. And then somebody (a few days before the deadline) says: “Does anybody know what this OutOfMemory exception means that I keep getting from time to time?” 

  8. With declared services, you only have to add osgi.annotation to the build path to make bnd generate this file. Handling the OSGi annotations is built-in and doesn’t require the configuration of a plugin. 

  9. You can make Felix DM invoke a method, too. 

  10. You don’t need an “unset” method for ordinary (non-static) attributes because the instance of the service component itself is discarded when a component is deactivated. 

  11. Neil Bartlett wrote on stackoverflow:

    You almost certainly don’t want to use ComponentFactory, so I recommend ignoring it.

    I’m not sure if this comment is correct, I can imagine use cases for both.