Skip to content

Latest commit

 

History

History
707 lines (593 loc) · 39.3 KB

components.md

File metadata and controls

707 lines (593 loc) · 39.3 KB
title
Apache Celix Components

Apache Celix Components

In Apache Celix, components are plain old C/C++ objects (POCOs) managed by the Apache Celix Dependency Manager (DM). Components can provide services and depend on services. Components are configured declarative using the DM api.

Service dependencies will influence the component's lifecycle as a component will only be active when all required dependencies are available.
The DM is responsible for managing the component's service dependencies, the component's lifecycle and when to register/unregister the component's provided services.

Note that the Apache Celix Dependency Manager is inspired by the Apache Felix Dependency Manager, adapted to Apache Celix and the C/C++ usage.

Component Lifecycle

Each component has its own lifecycle. A component's lifecycle state model is depicted in the state diagram below.

Component Life Cycle

The DM can be used to configure a component's lifecycle callbacks, the following component's lifecycle callbacks can be configured:

  • init
  • start
  • stop
  • deinit

These callbacks are used in the intermediate component's lifecycle states Initializing, Starting, Suspending, Resuming, Stopping and Deinitializing and the lifecycle callbacks are always called from the Celix event thread.

A component has the following lifecycle states:

  • Inactive: The component is inactive and the DM is not managing the component yet.
  • Waiting For Required: The component is waiting for required service dependencies.
  • Initializing: The component has found its required dependencies and is initializing by calling the init callback.
  • Initialized And Waiting For Required: The component has been initialized, but is waiting for required dependencies. _Note: that this can mean that:
    • During the init callback, 1 or more unavailable required service dependencies where added.
    • The component was active, but 1 or more required service dependency where removed and as result the component is not active anymore.
  • Starting: The component has found its required dependencies and is starting by calling the start callback and registering the components provided services.
  • Tracking Optional: The component has found its required dependencies and is started. It is still tracking for additional optional and required services.
  • Suspending: The component has found its required dependencies, but is suspending to prepare for a service change by unregistering the components provided service and calling the stop callback.
  • Suspended: The component has found its required dependencies and is suspended so that a service change can be processed.
  • Resuming: The component has found its required dependencies, a service change has been processed, and it is resuming by calling the start callback and registering the components provided services.
  • Stopping: The component has lost one or more of its required dependencies and is stopping by unregistering the components provided service and calling the stop callback.
  • Deinitializing: The component is being removed and is deinitializing by calling the deinit callback.

Component API

The DM Component C api can be found in the celix_dm_component.h header and the C++ api can be found in the celix/dm/Component.h header.

Example: Create and configure component's lifecycle callbacks in C

The following example shows how a simple component can be created and managed with the DM in C. Because the component's lifecycle is managed by the DM, this also means that if configured correctly no additional code is needed to remove and destroy the DM component and its implementation.

Remarks for the C example:

  1. Although this is a C component. The simple component functions have been design for a component approach, using the component pointer as first argument.
  2. The component implementation can be any POCO, as long as its lifecycle and destroy function signature follow a component approach: A single argument, with as type the component implementation pointer and an int return for the component lifecycle functions and a void return for the component destroy function.
  3. Creates the DM component, but note that the DM component is not yet known to the DM. This makes it possible to first configure the DM component over multiple function calls, before adding it to the DM.
  4. Configures the component implementation in the DM component, so that the implementation pointer can be used in the configured component callbacks.
  5. Configures the component lifecycle callbacks to the DM Component. These callbacks should accept the component implementation as its only argument. The CELIX_DM_COMPONENT_SET_CALLBACKS marco is used instead of the celix_dmComponent_setCallbacks function so that the component implementation type can directly be used in the lifecycle callbacks (instead of void*).
  6. Configures the component destroy implementation callback to the Dm Component. This callback will be called when the DM component is removed from the DM and has become inactive. The callback will be called from the Celix event thread. The advantages of configuring this callback is that the DM manages when the callback needs to be called; this removes some complexity for the users. The CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION marco is used instead of the celix_dmComponent_setImplementationDestroyFunction function so that the component implementation type can be directly used in the callback (instead of void*).
  7. Adds the DM Component the DM and as result the DM will that point on manage the components' lifecycle, service dependencies and provided services.
  8. No additional code is needed to clean up components and as such no activator stop callback function needs to be configured. The generated bundle activator will ensure that all components are removed from the DM when the bundle is stopped and the DM will ensure that the components are deactivated and destroyed correctly.
//src/simple_component_activator.c
#include <stdio.h>
#include <celix_bundle_activator.h>
#include <celix_dm_component.h>

//********************* COMPONENT *******************************/

typedef struct simple_component {
    int transitionCount; //not protected, only updated and read in the celix event thread.
} simple_component_t;

static simple_component_t* simpleComponent_create() {
    simple_component_t* cmp = calloc(1, sizeof(*cmp));
    cmp->transitionCount = 1;
    return cmp;
}

static void simpleComponent_destroy(simple_component_t* cmp) {
    free(cmp);
}

static int simpleComponent_init(simple_component_t* cmp) { // <------------------------------------------------------<1>
    printf("Initializing simple component. Transition nr %i\n", cmp->transitionCount++);
    return 0;
}

static int simpleComponent_start(simple_component_t* cmp) {
    printf("Starting simple component. Transition nr %i\n", cmp->transitionCount++);
    return 0;
}

static int simpleComponent_stop(simple_component_t* cmp) {
    printf("Stopping simple component. Transition nr %i\n", cmp->transitionCount++);
    return 0;
}

static int simpleComponent_deinit(simple_component_t* cmp) {
    printf("De-initializing simple component. Transition nr %i\n", cmp->transitionCount++);
    return 0;
}


//********************* ACTIVATOR *******************************/

typedef struct simple_component_activator {
    //nop
} simple_component_activator_t;

static celix_status_t simpleComponentActivator_start(simple_component_activator_t *act, celix_bundle_context_t *ctx) {
    //creating component
    simple_component_t* impl = simpleComponent_create(); // <--------------------------------------------------------<2>

    //create and configuring component and its lifecycle callbacks using the Apache Celix Dependency Manager
    celix_dm_component_t* dmCmp = celix_dmComponent_create(ctx, "simple_component_1"); // <--------------------------<3>
    celix_dmComponent_setImplementation(dmCmp, impl); // <-----------------------------------------------------------<4>
    CELIX_DM_COMPONENT_SET_CALLBACKS(
            dmCmp,
            simple_component_t,
            simpleComponent_init,
            simpleComponent_start,
            simpleComponent_stop,
            simpleComponent_deinit); // <----------------------------------------------------------------------------<5>
    CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(
            dmCmp,
            simple_component_t,
            simpleComponent_destroy); // <---------------------------------------------------------------------------<6>

    //Add dm component to the dm.
    celix_dependency_manager_t* dm = celix_bundleContext_getDependencyManager(ctx);
    celix_dependencyManager_add(dm, dmCmp); // <---------------------------------------------------------------------<7>
    return CELIX_SUCCESS;
}

CELIX_GEN_BUNDLE_ACTIVATOR(simple_component_activator_t, simpleComponentActivator_start, NULL) // <------------------<8>

Example: Create and configure component's lifecycle callbacks in C++

The following example shows how a simple component can be created and managed with the DM in C++. For C++ the DM will manage the component and also ensures that component implementation is kept in scope for as long as the component is managed by the DM.

Remarks for the C++ example:

  1. For C++ the DM can directly work on classes and as result lifecycle callback can be class methods.
  2. Creates a component implementation using a unique_ptr.
  3. Create a C++ DM Component and directly add it to the DM. For C++ DM Component needs to be "build" first, before the DM will manage them. This way C++ components can be build using a fluent api and marked complete with a build() method call. For a component implementation the DM accepts a unique_ptr, a shared_ptr, a value type or no implementation. If no implementation is provided the DM will create a component implementation using the template argument and assuming a default constructor (e.g. ctx->getDependencyManager()->createComponent<CmpWithDefaultCTOR>()).
  4. Configures the component lifecycle callbacks as class methods. The DM will call these callbacks using the component implementation raw pointer as object instance (this).
  5. "Builds" the component. C++ components will only be managed by the DM after they are build. This makes it possible to configure a component over multiple method calls before marking the component complete (build). The generated C++ bundle activator will also enable all components created during the bundle activation, this is done to ensure that the build behaviour is backwards compatible with previous released DM implementation. It is preferred that users explicitly build their components when they are completely configured.
//src/SimpleComponentActivator.cc
#include <celix/BundleActivator.h>

class SimpleComponent {
public:
    void init() { // <-----------------------------------------------------------------------------------------------<1>
        std::cout << "Initializing simple component. Transition nr " << transitionCount++ << std::endl;
    }

    void start() {
        std::cout << "starting simple component. Transition nr " << transitionCount++ << std::endl;
    }

    void stop() {
        std::cout << "Stopping simple component. Transition nr " << transitionCount++ << std::endl;
    }

    void deinit() {
        std::cout << "De-initializing simple component. Transition nr " << transitionCount++ << std::endl;
    }
private:
    int transitionCount = 1; //not protected, only updated and read in the celix event thread.
};

class SimpleComponentActivator {
public:
    explicit SimpleComponentActivator(const std::shared_ptr<celix::BundleContext>& ctx) {
        auto cmp = std::make_unique<SimpleComponent>(); // <---------------------------------------------------------<2>
        ctx->getDependencyManager()->createComponent(std::move(cmp), "SimpleComponent1") // <------------------------<3>
                .setCallbacks(
                        &SimpleComponent::init,
                        &SimpleComponent::start,
                        &SimpleComponent::stop,
                        &SimpleComponent::deinit) // <---------------------------------------------------------------<4>
                .build(); // <---------------------------------------------------------------------------------------<5>
    }
};

CELIX_GEN_CXX_BUNDLE_ACTIVATOR(SimpleComponentActivator)

Component's Provided Services

Components can be configured to provide services. These provided services will result in service registrations when a component is Starting or Resuming (i.e. when a component goes to the Tracking Optional state).

If a component provide services, these services will have an additional automatically added service property - named "component.uuid" - next to its configured provided service properties. The "component.uuid" service property can be used to identify if a service is provided by a component and which component.

Example: Component with a provided service in C

The following example shows how a component that provide a celix_shell_command service.

Remarks for the C example:

  1. C services do not support inheritance. So even if a C component provides a certain service, it is not an instance of said service. This also means the C service struct provided by a component needs to be stored separately. In this example this is done storing the service struct in the bundle activator data. Note that the bundle activator data "outlives" the component, because all components are removed before a bundle is completely stopped.
  2. Configures a provided service (interface) for the component. The service will not directly be registered, but instead will be registered in the component states Starting and Resuming.
 //src/component_with_provided_service_activator.c
#include <stdlib.h>
#include <celix_bundle_activator.h>
#include <celix_shell_command.h>

//********************* COMPONENT *******************************/

typedef struct component_with_provided_service {
    int callCount; //atomic
} component_with_provided_service_t;

static component_with_provided_service_t* componentWithProvidedService_create() {
    component_with_provided_service_t* cmp = calloc(1, sizeof(*cmp));
    return cmp;
}

static void componentWithProvidedService_destroy(component_with_provided_service_t* cmp) {
    free(cmp);
}

static bool componentWithProvidedService_executeCommand(
        component_with_provided_service_t *cmp,
        const char *commandLine,
        FILE *outStream,
        FILE *errorStream CELIX_UNUSED) {
    int count = __atomic_add_fetch(&cmp->callCount, 1, __ATOMIC_SEQ_CST);
    fprintf(outStream, "Hello from cmp. command called %i times. commandLine: %s\n", count, commandLine);
    return true;
}

//********************* ACTIVATOR *******************************/

typedef struct component_with_provided_service_activator {
    celix_shell_command_t shellCmd; // <-----------------------------------------------------------------------------<1>
} component_with_provided_service_activator_t;

static celix_status_t componentWithProvidedServiceActivator_start(component_with_provided_service_activator_t *act, celix_bundle_context_t *ctx) {
    //creating component
    component_with_provided_service_t* impl = componentWithProvidedService_create();

    //create and configuring component and its lifecycle callbacks using the Apache Celix Dependency Manager
    celix_dm_component_t* dmCmp = celix_dmComponent_create(ctx, "component_with_provided_service_1");
    celix_dmComponent_setImplementation(dmCmp, impl);
    CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(
            dmCmp,
            component_with_provided_service_t,
            componentWithProvidedService_destroy);

    //configure provided service
    act->shellCmd.handle = impl;
    act->shellCmd.executeCommand = (void*)componentWithProvidedService_executeCommand;
    celix_properties_t* props = celix_properties_create();
    celix_properties_set(props, CELIX_SHELL_COMMAND_NAME, "hello_component");
    celix_dmComponent_addInterface(
            dmCmp,
            CELIX_SHELL_COMMAND_SERVICE_NAME,
            CELIX_SHELL_COMMAND_SERVICE_VERSION,
            &act->shellCmd,
            props); // <---------------------------------------------------------------------------------------------<2>


    //Add dm component to the dm.
    celix_dependency_manager_t* dm = celix_bundleContext_getDependencyManager(ctx);
    celix_dependencyManager_add(dm, dmCmp);
    return CELIX_SUCCESS;
}

CELIX_GEN_BUNDLE_ACTIVATOR(
        component_with_provided_service_activator_t,
        componentWithProvidedServiceActivator_start,
        NULL)

Example: Component with a provided service in C++

The following example shows how a C++ component that provide a C++ celix::IShellCommand service and a C celix_shell_command service. For a C++ component it's possible to provide C and C++ services.

Remarks for the C++ example:

  1. If a component provides a C++ services, it also expected that the component implementation inherits the service interface.
  2. The overridden executeCommand method of celix::IShellCommand.
  3. Methods of C service interfaces can be implemented as class methods, but the bundle activator should ensure that the underlining C service interface structs are assigned with compatible C function pointers.
  4. Creating a component using only a template argument. The DM will construct - using a default constructor - a component implementation instance.
  5. Configures the component to provide a C++ celix::IShellCommand service. Note that because the component implementation is an instance of celix::IShellCommand no additional storage is needed. The service will not directly be registered, but instead will be registered in the components states Starting and Resuming.
  6. Set the C executeCommand function pointer of the celix_shell_command_t service interface struct to a capture-less lambda expression. The lambda expression is used to forward the call to the executeCCommand class method. Note the capture-less lambda expression can decay to C-style function pointers.
  7. Configures the component to provide a C celix_shell_command_t service. Note that for a C service, the createUnassociatedProvidedService must be used, because the component does not inherit celix_shell_command_t. The service will not directly be registered, but instead will be registered in the component states Starting and Resuming.
  8. "Build" the component so the DM will manage the component.
//src/ComponentWithProvidedServiceActivator.cc
#include <celix/BundleActivator.h>
#include <celix/IShellCommand.h>
#include <celix_shell_command.h>

class ComponentWithProvidedService : public celix::IShellCommand { // <----------------------------------------------<1>
public:
    ~ComponentWithProvidedService() noexcept override = default;

    void executeCommand(
            const std::string& commandLine,
            const std::vector<std::string>& /*commandArgs*/,
            FILE* outStream,
            FILE* /*errorStream*/) override {
        fprintf(outStream, "Hello from cmp. C++ command called %i times. commandLine is %s\n", 
                cxxCallCount++, commandLine.c_str());
    } // <-----------------------------------------------------------------------------------------------------------<2>

    void executeCCommand(const char* commandLine, FILE* outStream) {
        fprintf(outStream, "Hello from cmp. C command called %i times. commandLine is %s\n", cCallCount++, commandLine);
    } // <-----------------------------------------------------------------------------------------------------------<3>
private:
    std::atomic<int> cxxCallCount{1};
    std::atomic<int> cCallCount{1};
};

class ComponentWithProvidedServiceActivator {
public:
    explicit ComponentWithProvidedServiceActivator(const std::shared_ptr<celix::BundleContext>& ctx) {
        auto& cmp = ctx->getDependencyManager()->createComponent<ComponentWithProvidedService>(); // <---------------<4>

        cmp.createProvidedService<celix::IShellCommand>()
                .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <-----------------------------<5>

        auto shellCmd = std::make_shared<celix_shell_command_t>();
        shellCmd->handle = static_cast<void*>(&cmp.getInstance());
        shellCmd->executeCommand = [](void* handle, const char* commandLine, FILE* outStream, FILE*) -> bool {
            auto* impl = static_cast<ComponentWithProvidedService*>(handle);
            impl->executeCCommand(commandLine, outStream);
            return true;
        }; // <------------------------------------------------------------------------------------------------------<6>

        cmp.createUnassociatedProvidedService(std::move(shellCmd), CELIX_SHELL_COMMAND_SERVICE_NAME)
                .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -------------------------------------<7>

        cmp.build(); // <--------------------------------------------------------------------------------------------<8>
    }
private:
};

CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithProvidedServiceActivator)

Component's Service Dependencies

Components can be configured to have service dependencies. These service dependencies will influence the component's lifecycle. Components can have optional and required service dependencies. When service dependencies are required the component can only be active if all required dependencies are available; where available means at least 1 matching service dependency is found.

When configuring service dependencies, callbacks can be configured for handling services that are being added, removed or for when a new highest ranking service is available.

Service dependency callbacks can be configured with 3 different types of argument signatures:

  • A single argument for the service pointer (raw pointer or shared_ptr);
  • A service pointer (raw pointer or shared_ptr) as first argument and the service properties as second argument.
  • A service pointer (raw pointer or shared_ptr) as first argument, the service properties as second argument and the bundle providing the service as third argument.

Service dependency callbacks will always be called from the Celix event thread.

A service change (injection/removal) can be handled by the component using a Locking-strategy or a suspend-strategy. This strategy can be configured per service dependency and expect the following behaviour from the component implementation:

  • Locking-strategy: The component implementation must ensure that the stored service pointers (and if applicable the service properties and its bundle) are protected using a locking mechanism (e.g. a mutex). This should ensure that services are no longer in use after they are removed (or replaced) from a component and thus can be safely deleted from memory.
  • Suspend-strategy: The DM will ensure that before service dependency callbacks are called, all provided services are (temporary) unregistered and the component is suspended (using the components' stop callback). This should mean that there are no active users - through the provided services or active threads - of the service dependencies anymore and that service changes can safely be handling without locking. The component implementation must ensure that after a stop callback there are no active threads, thread pools, timers, etc - that use service dependencies - are active anymore.

Example: Component with a service dependencies in C

The following example shows how a C component that has two service dependency on the celix_shell_command_t service.

One service dependency is a required dependency with a suspend-strategy and uses a set callback which ensure that a single service is injected and that is always the highest ranking service. Note that the highest ranking service can be NULL if there are no other matching services.

The other dependency is an optional dependency with a locking-strategy and uses a addWithProps and removeWithProps callback. These callbacks will be called for every celix_shell_command_t service being added/removed and will be called with not only the service pointer, but also the service properties.

Remarks for the C example:

  1. Creates a mutex to protect the cmdShells field which is configured with a locking-strategy service dependency.
  2. Updates the highestRankingCmdShell field without locking. Note that because the service dependency is configured with a suspend-strategy the componentWithServiceDependency_setHighestRankingShellCommand function will only be called when the component is in the Suspended state or when it is not in the Active compound state.
  3. Locks the mutex and adds the newly added service to the cmdShells list. Note that because the service dependency is configured with a locking-strategy the componentWithServiceDependency_addShellCommand and componentWithServiceDependency_removeShellCommand functions can be called from any component lifecycle state.
  4. Creates a new DM service dependency object. Note that the DM service dependency is not yet known to the DM Component.
  5. Configures for which service name the service dependency will track services for. Optionally it is also possible to fine tune the tracked service by providing a service version range and/or service filter.
  6. Configures the update strategy for the service dependency to suspend-strategy.
  7. Configures the service dependency as a required service dependency.
  8. Creates an empty service dependency callback options struct. This struct can be used to configure different service dependency callbacks.
  9. Configures the set service dependency callback to componentWithServiceDependency_setHighestRankingShellCommand
  10. Configures the dependency manager to use the callbacks configures in opts.
  11. Adds the DM service dependency object to the DM component object.
  12. Configures the update strategy for the service dependency to locking-strategy.
  13. Configures the service dependency as an optional service dependency.
  14. Configures the addWithProps service dependency callback to componentWithServiceDependency_addShellCommand.
//src/component_with_service_dependency_activator.c
#include <stdlib.h>
#include <celix_bundle_activator.h>
#include <celix_shell_command.h>

//********************* COMPONENT *******************************/

typedef struct component_with_service_dependency {
    celix_shell_command_t* highestRankingCmdShell; //only updated when component is not active or suspended
    celix_thread_mutex_t mutex; //protects cmdShells
    celix_array_list_t* cmdShells;
} component_with_service_dependency_t;

static component_with_service_dependency_t* componentWithServiceDependency_create() {
    component_with_service_dependency_t* cmp = calloc(1, sizeof(*cmp));
    celixThreadMutex_create(&cmp->mutex, NULL); // <-----------------------------------------------------------------<1>
    cmp->cmdShells = celix_arrayList_create();
    return cmp;
}

static void componentWithServiceDependency_destroy(component_with_service_dependency_t* cmp) {
    celix_arrayList_destroy(cmp->cmdShells);
    celixThreadMutex_destroy(&cmp->mutex);
    free(cmp);
}

static void componentWithServiceDependency_setHighestRankingShellCommand(
        component_with_service_dependency_t* cmp,
        celix_shell_command_t* shellCmd) {
    printf("New highest ranking service (can be NULL): %p\n", shellCmd);
    cmp->highestRankingCmdShell = shellCmd; // <---------------------------------------------------------------------<2>
}

static void componentWithServiceDependency_addShellCommand(
        component_with_service_dependency_t* cmp,
        celix_shell_command_t* shellCmd,
        const celix_properties_t* props) {
    long id = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1);
    printf("Adding shell command service with service.id %li\n", id);
    celixThreadMutex_lock(&cmp->mutex); // <-------------------------------------------------------------------------<3>
    celix_arrayList_add(cmp->cmdShells, shellCmd);
    celixThreadMutex_unlock(&cmp->mutex);
}

static void componentWithServiceDependency_removeShellCommand(
        component_with_service_dependency_t* cmp,
        celix_shell_command_t* shellCmd,
        const celix_properties_t* props) {
    long id = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1);
    printf("Removing shell command service with service.id %li\n", id);
    celixThreadMutex_lock(&cmp->mutex);
    celix_arrayList_remove(cmp->cmdShells, shellCmd);
    celixThreadMutex_unlock(&cmp->mutex);
}

//********************* ACTIVATOR *******************************/

typedef struct component_with_service_dependency_activator {
    //nop
} component_with_service_dependency_activator_t;

static celix_status_t componentWithServiceDependencyActivator_start(component_with_service_dependency_activator_t *act, celix_bundle_context_t *ctx) {
    //creating component
    component_with_service_dependency_t* impl = componentWithServiceDependency_create();

    //create and configuring component and its lifecycle callbacks using the Apache Celix Dependency Manager
    celix_dm_component_t* dmCmp = celix_dmComponent_create(ctx, "component_with_service_dependency_1");
    celix_dmComponent_setImplementation(dmCmp, impl);
    CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(
            dmCmp,
            component_with_service_dependency_t,
            componentWithServiceDependency_destroy);

    //create mandatory service dependency with cardinality one and with a suspend-strategy
    celix_dm_service_dependency_t* dep1 = celix_dmServiceDependency_create(); // <-----------------------------------<4>
    celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL); // <-------------------<5>
    celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND); // <------------------------<6>
    celix_dmServiceDependency_setRequired(dep1, true); // <----------------------------------------------------------<7>
    celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; // <--<8>
    opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand; // <----------------------------<9>
    celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1); // <-------------------------------------------<10>
    celix_dmComponent_addServiceDependency(dmCmp, dep1); // <-------------------------------------------------------<11>

    //create optional service dependency with cardinality many and with a locking-strategy
    celix_dm_service_dependency_t* dep2 = celix_dmServiceDependency_create();
    celix_dmServiceDependency_setService(dep2, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL);
    celix_dmServiceDependency_setStrategy(dep2, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING);  // <----------------------<12>
    celix_dmServiceDependency_setRequired(dep2, false); // <--------------------------------------------------------<13>
    celix_dm_service_dependency_callback_options_t opts2 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS;
    opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand;  // <-------------------------------<14>
    opts2.removeWithProps = (void*)componentWithServiceDependency_removeShellCommand;
    celix_dmServiceDependency_setCallbacksWithOptions(dep2, &opts2);
    celix_dmComponent_addServiceDependency(dmCmp, dep2);

    //Add dm component to the dm.
    celix_dependency_manager_t* dm = celix_bundleContext_getDependencyManager(ctx);
    celix_dependencyManager_add(dm, dmCmp);
    return CELIX_SUCCESS;
}

CELIX_GEN_BUNDLE_ACTIVATOR(
        component_with_service_dependency_activator_t,
        componentWithServiceDependencyActivator_start,
        NULL)

Example: Component with a service dependencies in C++

The following example shows how a C++ component that has two service dependency. One service dependency for the C++ celix::IShellCommand service and one for the C celix_shell_command_t service.

The celix::IShellCommand service dependency is a required dependency with a suspend-strategy and uses a set callback which ensure that a single service is injected and that is always the highest ranking service. Note that the highest ranking service can be an empty shared_ptr if there are no service.

The celix_shell_command_t service dependency is an optional dependency with a locking-strategy and uses a addWithProperties and removeWithProperties callback. These callbacks will be called for every celix_shell_command_t service being added/removed and will be called with not only the service shared_ptr, but also the service properties.

Note that for C++ component service dependencies, there is no real different between a C++ or a C service dependency; In both cases the service pointers are injected using shared_ptr and if applicable the service properties and bundle argument are also provided as shared_ptr using the C++ celix::Properties and celix::Bundle.

Remarks for the C++ example:

  1. Creates a mutex to protect the shellCommands field which is configured with a locking-strategy service dependency.
  2. Updates the highestRankingShellCmd field without locking. Note that because the service dependency is configured with a suspend-strategy the ComponentWithServiceDependency::setHighestRankingShellCommand method will only be called when the component is in the Suspended state or when it is not in the Active compound state.
  3. Locks the mutex and adds the newly added service to the shellCommands list. Note that because the service dependency is configured with a locking-strategy the ComponentWithServiceDependency::addCShellCmd and ComponentWithServiceDependency::removeCShellCmd methods can be called from any component lifecycle state.
  4. Creates a new DM service dependency object, the service dependency is considered incomplete until the service dependency, component or DM is build. Note that the celix::dm::Component::createServiceDependency method is called without provided a service name, the service name will be inferred using the celix::typeName.
  5. Configures the service dependency set callback.
  6. Configures the service dependency as a required service dependency.
  7. Configures the update strategy for the service dependency to suspend-strategy.
  8. Creates another new DM service dependency object and in this case also explicitly provides the service name to use (CELIX_SHELL_COMMAND_SERVICE_NAME).
  9. Builds the component and as result also builds the components' service dependencies (i.e. marking them as complete).
//src/ComponentWithServiceDependencyActivator.cc
#include <celix/BundleActivator.h>
#include <celix/IShellCommand.h>
#include <celix_shell_command.h>

class ComponentWithServiceDependency {
public:
    void setHighestRankingShellCommand(const std::shared_ptr<celix::IShellCommand>& cmdSvc) {
        std::cout << "New highest ranking service (can be NULL): " << (intptr_t)cmdSvc.get() << std::endl;
        highestRankingShellCmd = cmdSvc; // <------------------------------------------------------------------------<2>
    }

    void addCShellCmd(
            const std::shared_ptr<celix_shell_command_t>& cmdSvc,
            const std::shared_ptr<const celix::Properties>& props) {
        auto id = props->getAsLong(celix::SERVICE_ID, -1);
        std::cout << "Adding shell command service with service.id: " << id << std::endl;
        std::lock_guard lck{mutex}; // <-----------------------------------------------------------------------------<3>
        shellCommands.emplace(id, cmdSvc);
    }

    void removeCShellCmd(
            const std::shared_ptr<celix_shell_command_t>& /*cmdSvc*/,
            const std::shared_ptr<const celix::Properties>& props) {
        auto id = props->getAsLong(celix::SERVICE_ID, -1);
        std::cout << "Removing shell command service with service.id: " << id << std::endl;
        std::lock_guard lck{mutex};
        shellCommands.erase(id);
    }
private:
    std::shared_ptr<celix::IShellCommand> highestRankingShellCmd{};
    std::mutex mutex{}; //protect shellCommands // <-----------------------------------------------------------------<1>
    std::unordered_map<long, std::shared_ptr<celix_shell_command_t>> shellCommands{};
};

class ComponentWithServiceDependencyActivator {
public:
    explicit ComponentWithServiceDependencyActivator(const std::shared_ptr<celix::BundleContext>& ctx) {
        using Cmp = ComponentWithServiceDependency;
        auto& cmp = ctx->getDependencyManager()->createComponent<Cmp>(); 

        cmp.createServiceDependency<celix::IShellCommand>() // <-----------------------------------------------------<4>
                .setCallbacks(&Cmp::setHighestRankingShellCommand) // <----------------------------------------------<5>
                .setRequired(true) // <------------------------------------------------------------------------------<6>
                .setStrategy(DependencyUpdateStrategy::suspend); // <------------------------------------------------<7>

        cmp.createServiceDependency<celix_shell_command_t>(CELIX_SHELL_COMMAND_SERVICE_NAME) // <--------------------<8>
                .setCallbacks(&Cmp::addCShellCmd, &Cmp::removeCShellCmd) 
                .setRequired(false)
                .setStrategy(DependencyUpdateStrategy::locking);

        cmp.build(); // <--------------------------------------------------------------------------------------------<9>
    }
};

CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithServiceDependencyActivator)

When will a component be suspended

Components will only suspend if:

  • The component is in the state Tracking Optional;
  • The component has at least 1 service dependency where the update strategy is configured as suspend-strategy;
  • There is a service update event ongoing, where the update service event matches 1 of the components' suspend-strategy service dependencies;
  • And least one of the component's matching suspend-strategy service dependency has a configured service injection/ removal callback configured.

The celix::dm shell command

To interactively see the available components, their current lifecycle state, provided service and service dependencies the celix::dm shell command can be used.

Examples of supported dm command lines are:

  • celix::dm - Show an overview of all components in the Celix framework. Only shows component lifecycle state.
  • dm - Same as celix::dm (as long as there is no colliding other dm commands).
  • dm full - Show a detailed overview of all components in the Celix framework. This also shows the provided services and service dependencies of each component.