Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

  1. The bundle activator which controls the life-cycle of the bundle.
  2. The actual service component implementation, which can be a POJO.

When using the dependency manager, your bundle activator is a subclass of DependencyActivatorBase. It needs to implement two life cycle methods: init and destroy. Both methods take two arguments: BundleContext and DependencyManager. The latter is your interface to the declarative API you can use to define your services components and dependencies.

The following paragraphs will show various examples that explain how to do this. Subsequently, some more advanced scenarios will be covered that involve listening to dependency and service component state changes and interacting with the OSGi framework from within your service component implementation.

Registering a service

The first example is about registering a service. We extend DependencyActivatorBase and in the init method we use the reference to the DependencyManager to create and add a servicecomponent. For this service component we subsequently set its service interface and implementation. In this case the interface is the Store interface, the second parameter, null, allows you to provide properties along with the service registration. For the implementation, we only mention the Class of the implementation, which means the dependency manager will lazily instantiate it. In this case, there is not much point in doing that because the service component has no dependencies, but if it had, the instantiation would only happen when those dependencies were resolved.

Notice that the dependency manager API uses method chaining to create a more or less "fluent" API that, with proper indentation, is very easy to read.

Code Block
public class Activator extends DependencyActivatorBase {
    public void init(BundleContext context, DependencyManager manager) throws Exception {
        manager.add(createServicecreateComponent()
            .setInterface(Store.class.getName(), null)
            .setImplementation(MemoryStore.class)
        );
    }
    
    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

...

Code Block
public class Activator extends DependencyActivatorBase {
    public void init(BundleContext context, DependencyManager manager) throws Exception {
        manager.add(createServicecreateComponent()
            .setImplementation(DataGenerator.class)
            .add(createServiceDependency()
                .setService(Store.class)
                .setRequired(true)
            )
            .add(createServiceDependency()
                .setService(LogService.class)
                .setRequired(false)
            )
        );
    }
    
    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

Now let's look at our POJO. There are a couple of interesting things to explain. First of all, our dependencies are declared as fields, and they don't even have setters (or getters). When the dependency manager instantiates our class, it will (through reflection) inject the dependencies so they are just available for our class to use. That is also the reason these fields are declared as volatile: to make sure they are visible for to all threads traversing our classinstance.

One final note, since we defined our LogService dependency as optional, it might not be available when we invoke it. Still, the code does not show contain any checks to avoid a null pointer exception. It does not need to, since the dependency manager makes sure to inject a null object when the real service is not available. The null object can be invoked and will do nothing. For a lot of cases that is good enough, but for those cases where it is not, our next example introduces callbacks that notify you of changes.

...

Sometimes, simply injecting services does not give you enough control over a dependency because you might want to track more than one, or you might want to execute some code on changes. For all those cases, callbacks are your friendfriends. Since one of our goals is to not introduce any kind of API in our POJO, callbacks are declared by specifying their method names instead of through some interface. In this case, we have a dependency on Translator services, and we define added and removed as callbacks.

Code Block
public class Activator extends DependencyActivatorBase {
    public void init(BundleContext context, DependencyManager manager) throws Exception {
        manager.add(createServicecreateComponent()
            .setImplementation(DocumentTranslator.class)
            .add(createServiceDependency()
                .setService(Translator.class)
                .setRequired(false)
                .setCallbacks("added", "removed")
            )
        );
    }
    
    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

...

Not all dependencies are on services. There are several other types of dependencies that are supported, one of them is the configuration dependency. In fact, only required configuration dependencies are supported, because optional ones can just be achieved by registering as a ManagedService yourself. When defining the dependency, you must define the persistent ID of the service. The service component will not become active until the configuration you depend on is available and is valid. The latter can be checked by your implementation as we will see below.

Code Block
public class Activator extends DependencyActivatorBase {
    public void init(BundleContext context, DependencyManager manager) throws Exception {
        manager.add(createServicecreateComponent()
            .setImplementation(Task.class)
            .add(createConfigurationDependency()
                .setPid("config.pid")
            )
        );
    }
    
    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

Here's our code that implements ManagedService and has an updated method. This method checks if the provided configuration is valid and throw a ConfigurationException if it is not. As long as this method does not accept the configuration, the corresponding service component will not be activated.

Code Block
public class Task implements ManagedService {
    private String m_interval;

    public void execute() {
        System.out.println("Scheduling task with interval " + m_interval);
    }

    public void updated(Dictionary properties) throws ConfigurationException {
        if (properties != null) {
            m_interval = (String) properties.get("interval");
            if (m_interval == null) {
                throw new ConfigurationException("interval", "must be specified");
            }
        }
    }
}

...