Dependency Manager - Components

Components are declared by the dependency manager and can be implemented by POJOs that contain no references to the OSGi framework whatsoever. Components are the main building blocks of your OSGi application. They have a life cycle, can register themselves as services and have zero or more dependencies.

You can either use the Java API or the Java Annotations and this reference section describes both.

Types of Components

There are different types of Dependency Manager components:

  • Component: Components are the main building blocks for OSGi applications. They can publish themselves as a service, and/or they can have dependencies. These dependencies will influence their life cycle as component will only be activated when all required dependencies are available.

  • Aspect Service: A service that provides a non-functional aspect on top of an existing service. In aspect oriented programming, an aspect, or interceptor can sit between a client and another target service used by the client. An Aspect Service first tracks a target service and is created once the target service is detected. Then the Aspect Service is provided, but with a higher ranking, and the client is transparently updated with the aspect. Aspects can be chained and may apply to the same target service (and in this case, the ranking of the Aspect service is used to chain aspects in the proper order).

  • Adapter Service: A Service that adapts another existing service into a new one. Like with aspects, sometimes you want to create adapters for certain services, which add certain behavior that results in the publication of (in this case) a different service. Adapters can dynamically be added and removed and allow you to keep your basic services implementations clean and simple, adding extra features on top of them in a modular way.

  • Bundle Adapter Service: creates an OSGi service a service on top of a given bundle.

  • Resource Adapter Service: creates an OSGi service on top of a specific Resource.

  • Factory Configuration Adapter Service: creates an OSGi service from ConfigAdmin, using a factoryPid, and a ManagedServiceFactory.

Life cycle

The dependency manager, as part of a bundle, shares the generic bundle life cycle explained in the OSGi specification. The life cycle of the dependency manager itself, and the components it manages, can be located inside the active state of the hosting bundle.

Each component you define gets its own life cycle, which is explained in the state diagram below.

State diagram

A component is associated with an instance. This instance can either be specified directly, or you can specify its class. If you do the latter, the actual instance will be created lazily.

Changes in the state of the component will trigger the following life cycle methods:

  • init,

  • start,

  • stop and

  • destroy.

The dependency manager will look for methods with these names and one of the following signatures in this order:

  • (Component),

  • ().

If you don’t specify anything, the methods with these names will be invoked on the instance. By using setCallbacks() you can however change this behavior: You can change the names of the methods to look for. Any methods that are set to ` null ` will not be invoked at all. Another thing you can do is to specify a different instance to invoke these methods on. If you do that, you will usually want to use the first signature, which gives you a reference to the ` Component ` whose life cycle method was invoked.

Here is a descrition of the component states:

  • Inactive state: The Component is defined, but not enabled (not yet added to a DependencyManager object, or the bundle has been stopped).

  • Waiting for required state: The Component is enabled (has been added to a DependencyManager object) and the required dependencies declared in the Activator are tracked. The component remains in the current state until all required dependencies are available.

  • Instantiated and waiting for required state: All required dependencies declared in the Activator are available. The Component has been instantiated with required dependencies injected, and has been invoked in the init callback. Now, if some extra required dependencies have been dynamically added in the init callback, then the component remains in the current state until all extra required dependencies become available.

  • Tracking optional: All Required dependencies are injected (including the ones you dynamically added from the init method), the component start callback is called, the component service is registered in the OSGi registry, and finally the optional dependencies (with callbacks) are then tracked.

What happens during component instantiation ?

  1. The service is instantiated.

  2. The following special objects are injected through reflection on class fields, if they exist (but you can deactivate this behavior from the API):

    • BundleContext

    • ServiceRegistration

    • DependencyManager

    • Component

  3. autoconfig dependencies are injected through reflection on class fields, if they exist. If an autoconfig optional dependency is unavailable, a NullObject is then injected.

  4. Required dependency callbacks defined from the Activator are called.

  5. The component init method is called, and from that method you can then dynamically add more dependencies by using the Component parameter passed to the init method, or using a class field of Component type (which in this case has been injected during step 2).

  6. When all required dependencies (including dependencies dynamically added from the init method) are available, they are injected (using callbacks, or autoconfig).

  7. The component start callback is invoked.

  8. Optional dependencies (with callbacks) are then tracked.

  9. The component service(s) is then registered in the OSGi service registry

When using Annotations, there are some specific behaviors:

  • The @init method may return a Map that contains filters in order to dynamically configure dependencies annotated with a name attribute, and the dependencies will then be injected after the @init method (exactly if you would have added the dependencies from the init method using the API).

  • The @start method may return a Map in order to dynamically add more service properties (if the component provides some services).

  • The component can be dynamically stopped or restarted using a special @LifecycleController annotation.

Interfaces and properties

Components in the context of the dependency manager can be published as OSGi services under one or more interface names, plus optionally a set of properties. This is no different than a normal OSGi service. It’s important to mention that you don’t have to register a service. If you don’t, you basically created a component that can do work and have dependencies and a managed life cycle.

Composition

When implementing more complex components, you often find yourself using more than one instance. However, several of these instances might want to have dependencies injected. In such cases you need to tell the dependency manager which instances to consider. This has to be a fixed set of instances however.

We now describe how to declare a service composition using the Api, and the Annotations:

Example:

When using the DependencyManager API, you can use the Component.setComposition method to declare a special callback in your component that returns the list of object that are part of the component, and all dependencies and lifecycle callbacks will be invoked on the objects returned by the method. Let’s take an example, with a ProviderImpl top-level service implementation that is internally implemented using three Pojos: ProviderImpl, ProviderParticipant1, and ProviderParticipant2:

public class ProviderImpl implements Provider {
    private final ProviderParticipant1 m_participant1 = new ProviderParticipant1();
    private final ProviderParticipant2 m_participant2 = new ProviderParticipant2();
    private volatile LogService m_log; // injected

    Object[] getComposition() {
        return new Object[] { this, m_participant1, m_participant2 };
    }

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderImpl.start(): participants=" + m_participant1 + "," + m_participant2
            + ", conf=" + m_conf);
    }
}

public class ProviderParticipant1 {
    private volatile LogService m_log; // also injected since we are part of the composition

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderParticipant1.start()");
    }
}

public class ProviderParticipant2 {
    private volatile LogService m_log; // also injected since we are part of the composition

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderParticipant2.start()");
    }
}

And here is the Activator, which uses the setComposition method:

 public class Activator extends DependencyActivatorBase {
     public void init(BundleContext ctx, DependencyManager m) throws Exception {
         m.add(createComponent()
             .setImplementation(ProviderImpl.class)
             .setComposition("getComposition")
             .add(createServiceDependency().setService(LogService.class).setRequired(true)));
     }
 }

Factories

Out of the box, there already is support for lazy instantiation, meaning that the dependency manager can create component instances for you when their required dependencies are resolved. However, sometimes creating a single instance using a default constructor is not enough. In those cases, you can tell the dependency manager to delegate the creation process to a factory.

Interestingly, you can also mix the usage of a Factory object and a Composition of objects returned by the Factory. The following is the same example as in the previous section (Composition), but using a Factory approach in order to instantiate a composition of objects: The "ProviderFactory" is first injected with a Configuration that can possibly be used to create and configure all the other objects that are part of the composition; each object will also be injected with the dependencies defined in the Activator.

public class ProviderFactory {
    private ProviderParticipant1 m_participant1;
    private ProviderParticipant2 m_participant2;
    private ProviderImpl m_providerImpl;
    private Dictionary<String, String> m_conf;

    public void updated(Dictionary<String, String> conf) throws Exception {
        // validate configuration and throw an exception if the properties are invalid
        m_conf = conf;
    }

    /**
     * Builds the composition of objects used to implement the "Provider" service.
     * The Configuration injected by Config Admin will be used to configure the components
     * @return The "main" object providing the "Provider" service.
     */
    Object create() {
        // Here, we can instantiate our object composition based on our injected configuration ...

        if ("true".equals(m_conf.get("some.parameter")) {
            m_participant1 = new ProviderParticipant1(); // depenencies and lifecycle callbacks will also be applied
            m_participant2 = new ProviderParticipant2(); // depenencies and lifecycle callbacks will also be applied
        } else {
            // Compose with some other objects ...
            m_participant1 = new ProviderParticipant3(); // depenencies and lifecycle callbacks will also be applied
            m_participant2 = new ProviderParticipant4(); // depenencies and lifecycle callbacks will also be applied
        }

        m_providerImpl = new ProviderImpl(m_participant1, m_participant2);
        return m_providerImpl; // Main object implementing the Provider service
    }

    /**
     * Returns the list of objects that are part of the composition for the Provider implementation.
     */
    Object[] getComposition() {
        return new Object[] { m_providerImpl, m_participant1, m_participant2 };
    }
}

And here is the Activator: notice the setFactory method that specifies the factory to use to create the implementation. Also pay attention to the setComposition method, which indicates the method to call in order to get all instances that are part of a composition and need dependencies injected:

 public class Activator extends DependencyActivatorBase {
     public void init(BundleContext ctx, DependencyManager m) throws Exception {
         ProviderFactory factory = new ProviderFactory();
         m.add(createComponent()
             .setFactory(factory, "create") // factory.create() will return the implementation instance
             .setComposition(factory, "getComposition")
             .add(createConfigurationDependency()
                 .setPid("some.pid")
                 .setCallback(factory, "updated")) // will invoke "updated" on the factory instance
             .add(createServiceDependency().setService(LogService.class).setRequired(true)));
     }
 }

You can refer to this sample code, which is part of the source distribution.