This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch pnoltes/feature/update_component_and_pattern_documentation in repository https://gitbox.apache.org/repos/asf/celix.git
commit 965f7c6e6f470b576cdcf47b64185ffb660ae2f5 Author: Pepijn Noltes <[email protected]> AuthorDate: Sun May 22 22:17:11 2022 +0200 Adds dm component document and examples. Also adds support for configuring a component implementation destroy function so that C component bundle activator can operate with less boilerplate code. --- documents/components.md | 693 +++++++++++++++++---- .../dm_example/phase1/src/phase1_activator.c | 10 +- .../dm_example/phase2a/src/phase2a_activator.c | 10 +- .../dm_example/phase2b/src/phase2b_activator.c | 6 +- .../dm_example/phase3/src/phase3_activator.c | 16 +- .../readme_c_examples/CMakeLists.txt | 20 + .../component_with_provided_service_activator.c | 90 +++ .../component_with_service_dependency_activator.c | 123 ++++ .../src/simple_component_activator.c | 92 +++ .../readme_cxx_examples/CMakeLists.txt | 22 +- .../src/ComponentWithProvidedServiceActivator.cc | 69 ++ .../src/ComponentWithServiceDependencyActivator.cc | 75 +++ .../src/SimpleComponentActivator.cc | 57 ++ .../gtest/src/DependencyManagerTestSuite.cc | 37 ++ libs/framework/include/celix_bundle_activator.h | 35 +- libs/framework/include/celix_dependency_manager.h | 12 +- libs/framework/include/celix_dm_component.h | 55 +- libs/framework/src/dm_component_impl.c | 24 +- 18 files changed, 1256 insertions(+), 190 deletions(-) diff --git a/documents/components.md b/documents/components.md index f0dcb5dc..5f8c96fb 100644 --- a/documents/components.md +++ b/documents/components.md @@ -19,203 +19,626 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Apache Celix Components - TODO refactor this documentation file +TODO also describes when cmp is suspended: there is a svc dep with suspend-strategy and the set or add/rem +callback is configured. -## Introduction - -The Dependency Manager contains a static library which can be used to manage (dynamic) services on a higher abstraction level in a declarative style. -The Apache Celix Dependency Manager is inspired by the [Apache Felix Dependency Manager](http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html). - -## Components - -Components are the main building blocks for OSGi applications. They can publish services, 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. - -Within Apache Celix a component is expected to have a set of functions where the first argument is a handle to the component (e.g. self/this). How this is achieved is up the the user, for some examples how this can be done see the example in the Apache Celix Project. - -The Dependency Manager, as part of a bundle, shares the generic bundle life cycle explained in the OSGi specification. -Each component you define gets its own life cycle. The component life cycle is depicted in the state diagram below. +# 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 service and have services dependencies. 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](http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html), adapted to Apache Celix +and the C/C++ usage. + +# Component Lifecycle +Each DM Component you define gets its own lifecycle. +A component's lifecycle state model is depicted in the state diagram below. + + + +The DM can be used to configure a component's lifecycle callbacks; Lifecycle callbacks are always called from the +Celix event thread. +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`. + +A DM 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: + 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 `init` callback new service dependencies was added or that the component + was active, but a required service dependency is removed and as result the component is not active anymore._ +- `Starting`: _The component has found its required dependencies and is starting: Calling the `start` callback and + registering it's 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: + Unregistering its 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: Calling the `start` callback and registering its provided services. +- `Stopping`: _The component has lost one or more of its required dependencies and is stopping: Unregistering its + provided service and calling the `stop` callback._ +- `Deinitializing`: _The component is being removed and is deinitializing: Calling the `deinit` callback._ + +## 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 follow a + component approach (one argument with the component implementation pointer as type) + 3. Creates the DM component, but note that this component is not yet known to the DM. This makes it possible to + first configure the DM component over multiple function calls, before adding - and as result + activating - the DM component 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 activate manage the component. + 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 component are removed from the DM when the + bundle is stopped and the DM will ensure that the components are deactivated and destroyed correctly. + +```C +//src/simple_component_activator.c +#include <stdio.h> +#include <celix_api.h> - +//********************* COMPONENT *******************************/ -Changes in the state of the component will trigger the following life cycle callback functions: +typedef struct simple_component { + int transitionCount; //not protected, only updated and read in the celix event thread. +} simple_component_t; - `init`, - `start`, - `stop` and - `deinit`. +static simple_component_t* simpleComponent_create() { + simple_component_t* cmp = calloc(1, sizeof(*cmp)); + cmp->transitionCount = 1; + return cmp; +} -The callback functions can be specified by using the component_setCallbacks. +static void simpleComponent_destroy(simple_component_t* cmp) { + free(cmp); +} -## DM Parts +static int simpleComponent_init(simple_component_t* cmp) { // <------------------------------------------------------<1> + printf("Initializing simple component. Transition nr %i\n", cmp->transitionCount++); + return 0; +} -The Dependency Manager consist out of four main parts: `DM (Dependency Manager) Activator`, `Dependency Manager`, `DM Component` and `DM Service Dependency`. +static int simpleComponent_start(simple_component_t* cmp) { + printf("Starting simple component. Transition nr %i\n", cmp->transitionCount++); + return 0; +} -### DM Activator +static int simpleComponent_stop(simple_component_t* cmp) { + printf("Stopping simple component. Transition nr %i\n", cmp->transitionCount++); + return 0; +} -The `DM Activator` implements a "normal" Celix bundle activator and depends on four functions which needs to be implemented by the user of the Dependency Manager: -- `dm_create` : Should be used to allocated and initialize a dm activator structure. If needed this structure can be used to store object during the lifecycle of the bundle. -- `dm_init` : Should be used to interact with the `Dependency Manager`. Here a user can components, service dependencies and provided services. -- `dm_destroy` : Should be used to deinitialize and deallocate objects created in the `dm_create` function. +static int simpleComponent_deinit(simple_component_t* cmp) { + printf("De-initializing simple component. Transition nr %i\n", cmp->transitionCount++); + return 0; +} -### Dependency Manager +//********************* 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; +} -The `Dependency Manager` act as an entry point to add or remove DM Components. The `Dependency Manager` is provided to the `dm_init` function. +CELIX_GEN_BUNDLE_ACTIVATOR(simple_component_activator_t, simpleComponentActivator_start, NULL) // <------------------<8> +``` -### DM Component +## 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 components and also ensures that component implementations are kept in scope for as +long as the components are 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 activate them. This way C++ components can be build using a fluent api and marked ready with a `build()` + method call. + As component implementation the DM accepts a unique_pt, shared_ptr, value type of 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 as object instance. +5. "Builds" the component. C++ component 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 as ready (build). + The generated C++ bundle activator will also enable all components created during the bundle activation, 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. + +```C++ +//src/SimpleComponentActivator.cc +#include <celix/BundleActivator.h> + +class SimpleComponent { +public: + void init() { // <-----------------------------------------------------------------------------------------------<1> + std::cout << "Initializing simple component. Transition nr " << transitionCount++ << std::endl; + } -The `DM Component` manages the life cycle of a component. For example, when all required service dependencies are available the `DM Component` will call the `start` specified callback function of the component. + void start() { + std::cout << "starting simple component. Transition nr " << transitionCount++ << std::endl; + } -The `component_setImplementation` function can be used to specify which component handle to use. -The `component_addInterface` can be used to specify one additional service provided by the component. -The `component_addServiceDependency` can be used to specify one additional service dependency. + void stop() { + std::cout << "Stopping simple component. Transition nr " << transitionCount++ << std::endl; + } -### Dm Service Dependency + 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. +}; -The `DM Service Dependency` can be used to specify service dependencies for a component. i +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> + } +}; -When these dependencies are set to required the `DM Component` will ensure that components will only be started when all required dependencies are available and stop the component if any of the required dependencies are removed. -This feature should prevent a lot of boiler plating code compared to using a service tracker or services references directly. +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(SimpleComponentActivator) +``` -A service dependency update strategy can also be specified. Default this strategy is set to `DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND` this strategy will stop and start (suspend) a component when any of the specified service dependencies change (are removed, added or modified). -When correctly used this strategy removes the need for locking services during updates/invocation. See the dependency manager example for more details. +# Component's Provided Services +Components can be configured to provide services. These provided services will correspondent to service registration +when a component is in the `Tracking Optional` state. -The `serviceDependency_setCallbacks` function can be used to specify the function callback used when services are added, set, removed or modified. -The `serviceDependency_setRequired` function can be used to specify if a service dependency is required. -The `serviceDependency_setStrategy` function can be used to specify a service dependency update strategy (suspend or locking). +If a component provide services, these service will have an additional metadata property - named "component.uuid" +that couples the services to the component named "component.uuid". -### Snippets +## Example: Component with a provided service in C +The following example shows how a component that provide a `celix_shell_command` service. -#### DM Bundle Activator +Remarks for the C example: +1. C and 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 if the component enters the state `Tracking Optional`. -The next snippet shows a dm bundle activator and how to add components to the dependency manager. ```C - -//exmpl_activator.c -#include <dm_activator.h> + //src/component_with_provided_service_activator.c #include <stdlib.h> +#include <celix_api.h> +#include <celix_shell_command.h> -struct dm_exmpl_activator { - exmpl_t* exmpl; -}; - -celix_status_t dm_create(bundle_context_pt context, void **userData) { - *userData = calloc(1, sizeof(struct dm_exmpl_activator)); - return *userData != NULL ? CELIX_SUCCESS : CELIX_ENOMEM; -} - -celix_status_t dm_init(void * userData, bundle_context_pt context, dm_dependency_manager_pt manager) { - celix_status_t status = CELIX_SUCCESS; - struct dm_exmpl_activator *act = (struct dm_exmpl_activator*)userData; +//********************* COMPONENT *******************************/ - act->exmpl = exmpl_create(); - if (act->exmpl != NULL) { - dm_component_pt cmp; - component_create(context, "Example Component", &cmp); - component_setImplementation(cmp, act->exmpl); +typedef struct component_with_provided_service { + int callCount; //atomic +} component_with_provided_service_t; - dependencyManager_add(manager, cmp); - } else { - status = CELIX_ENOMEM; - } - - return status; +static component_with_provided_service_t* componentWithProvidedService_create() { + component_with_provided_service_t* cmp = calloc(1, sizeof(*cmp)); + return cmp; } -celix_status_t dm_destroy(void * userData, bundle_context_pt context, dm_dependency_manager_pt manager) { - celix_status_t status = CELIX_SUCCESS; - struct dm_exmpl_activator *act = (struct dm_exmpl_activator*)userData; +static void componentWithProvidedService_destroy(component_with_provided_service_t* cmp) { + free(cmp); +} - if (act->exmpl != NULL) { - exmpl_destroy(act->exmpl); - } - free(act); +static bool componentWithProvidedService_executeCommand( + component_with_provided_service_t *cmp, + const char *commandLine, + FILE *outStream, + FILE *errorStream __attribute__((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; -} -``` - -### References +} -For more information examples please see +CELIX_GEN_BUNDLE_ACTIVATOR( + component_with_provided_service_activator_t, + componentWithProvidedServiceActivator_start, + NULL) +``` -- [The Dependency Manager API](../../libs/framework/include/celix/dm): The dependency manager header files -- [Getting Started: Using Service with C](../getting_started/using_services_with_c.md): A introduction how to work with services using the dependency manager -- [Dm example](../../examples/celix-examples/dm_example): A DM example. +## 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 `elix_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 if the component enters the state `Tracking Optional`. +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 function pointers. +7. Configures the component to provide a C `celix_shell_command_t` service. Note that for a C service, the service + interface struct also needs to be stored to ensure that the service interface struct will outlive the component. + The service will not directly be registered, but instead will be registered if the component enters the state + `Tracking Optional`.. +8. "Build" the component so the DM will manage and try to activate the component. + + +```C++ +//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}; +}; -## Dependency Manager Shell support +class ComponentWithProvidedServiceActivator { +public: + explicit ComponentWithProvidedServiceActivator(const std::shared_ptr<celix::BundleContext>& ctx) { + auto& cmp = ctx->getDependencyManager()->createComponent<ComponentWithProvidedService>(); // <---------------<4> -There is support for retrieving information of the dm components with -use of the `dm` command. This command will print all known dm component, -their state, provided interfaces and required interfaces. + cmp.createProvidedService<celix::IShellCommand>() + .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <---------------------------------<5> + 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.createProvidedCService(&shellCmd, CELIX_SHELL_COMMAND_SERVICE_NAME) + .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -----------------------------------------<7> -# Apache Celix C++ Dependency Manager + cmp.build(); // <--------------------------------------------------------------------------------------------<8> + } +private: + celix_shell_command_t shellCmd{nullptr, nullptr}; +}; -## Introduction +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithProvidedServiceActivator) +``` -The C++ Dependency Manager contains a static library which can be used to manage (dynamic) services on a higher abstraction level in a declarative style. -The Apache Celix C++ Dependency Manager is inspired by the [Apache Felix Dependency Manager](http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html). +# 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; + +When configuring service dependencies, callbacks can be configured when services are being added, removed or when a +new highest ranking service is available. It is also possible to configure callbacks which only inject/remove service +pointers, or service pointers with their properties (metadata) and even service pointers with their properties and +their providing bundle. 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 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. +- 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 `stop` callback no active thread, 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 service. + +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 metadata properties. + +Remarks for the C example: +1. TODO -The C++ Dependency Manager uses fluent interface to make specifying DM components and service dependencies very concise and relies on features introduced in C++11. +```C +//src/component_with_service_dependency_activator.c +#include <stdlib.h> +#include <celix_api.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; + 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); + cmp->cmdShells = celix_arrayList_create(); + return cmp; +} -## C++ and C Dependency Manager +static void componentWithServiceDependency_destroy(component_with_service_dependency_t* cmp) { + celix_arrayList_destroy(cmp->cmdShells); + celixThreadMutex_destroy(&cmp->mutex); + free(cmp); +} -The C++ Dependency Manager is build on top of the C Dependency Manager. -To get a good overview of the C++ Dependency Manager please read the [Dependency Manager documentation](../../documents/components/README.md) +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; +} -## DM Parts +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); + celix_arrayList_add(cmp->cmdShells, shellCmd); + celixThreadMutex_unlock(&cmp->mutex); +} -The C++ Dependency Manager consist out of four main parts: `celix::dm::DmActivator`, `celix::dm::DependencyManager`, `celix::dm::Component` and `celix::dm::ServiceDependency`. +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); +} -### DmActivator +//********************* 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(); + celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL); + celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND); + celix_dmServiceDependency_setRequired(dep1, true); + celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand; + celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1); + celix_dmComponent_addServiceDependency(dmCmp, dep1); + + //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); + celix_dmServiceDependency_setRequired(dep2, false); + celix_dm_service_dependency_callback_options_t opts2 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand; + 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; +} -The `DmActivator` class should be inherited by a bundle specific Activator. +CELIX_GEN_BUNDLE_ACTIVATOR( + component_with_service_dependency_activator_t, + componentWithServiceDependencyActivator_start, + NULL) +``` -- The static `DmActivator::create` method needs to be implemented and should return a bundle specific subclass instance of the DmActivator. -- The `DmActivator::init` method should be overridden and can be used to specify which components to use in the bundle. -- The `DmActivator::deinit` method can be overridden if some cleanup is needed when a bundle is stopped. +## 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. -### Dependency Manager +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 `DependencyManager` act as an entry point to create (DM) Components. +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 metadata properties. -### Component +Note that for C++ component service dependencies there is no real different between a C++ or a C service; Both +are injected as shared_ptr, and as optional properties or bundle argument the C++ variant are provided. -The (DM) `Component` manages the life cycle of a component (of the template type T). For example, when all required service dependencies are available the `Component` will call the `start` specified callback function of the component. +Remarks for the C++ example: +1. TODO -- The `Component::setInstance` method can be used to set the component instance to used. If no instance is set the (DM) `Component` will (lazy) create a component instance using the default constructor. -- The `Component::addInterface` method can be used to specify one additional C++ service provided by the component. -- The `Component::addCInterface` method can be used to specify one additional C service provided by the component. -- The `Component::createServiceDependency` method can be used to specify one additional typed C++ service dependency. -- The `Component::createCServiceDependency` method can be used to specify one additional typed C service dependency. +```C++ +//src/ComponentWithServiceDependencyActivator.cc +#include <celix/BundleActivator.h> +#include <celix/IShellCommand.h> +#include <celix_shell_command.h> -### ServiceDependency and CServiceDependency +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; + } -The (DM) `ServiceDependency` can be used to specify C++ service dependencies for a component and the (DM) `CServiceDependency` can be used to specify C service dependencies for a component. + 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}; + shellCommands.emplace(id, cmdSvc); + } -When these dependencies are set to required the `Component` will ensure that components will only be started when all required dependencies are available and stop the component if any of the required dependencies are removed. -This feature should prevent a lot of boiler plating code compared to using a service tracker or services references directly. + 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 below + std::unordered_map<long, std::shared_ptr<celix_shell_command_t>> shellCommands{}; +}; -A service dependency update strategy can also be specified (suspend or locking. Default this strategy is set to `DependencyUpdateStrategy::suspend` this strategy will stop and start (suspend) a component when any of the specified service dependencies changes (are removed, added or modified). -When correctly used this strategy removes the need for locking services during updates/invocation. See the dependency manager_cxx example for more details. +class ComponentWithServiceDependencyActivator { +public: + explicit ComponentWithServiceDependencyActivator(const std::shared_ptr<celix::BundleContext>& ctx) { + using Cmp = ComponentWithServiceDependency; + auto& cmp = ctx->getDependencyManager()->createComponent<Cmp>(); // <-------------<1> -- The `(C)ServiceDependency::setCallbacks` methods can be used to specify the function callback used when services are added, set, removed or modified. -- The `(C)ServiceDependency::setRequired` methods can be used to specify if a service dependency is required. -- The `(C)ServiceDependency::setStrategy` methods can be used to specify the service dependency update strategy (suspend or locking). + cmp.createServiceDependency<celix::IShellCommand>() + .setCallbacks(&Cmp::setHighestRankingShellCommand) + .setRequired(true) + .setStrategy(DependencyUpdateStrategy::suspend); -### References + cmp.createServiceDependency<celix_shell_command_t>(CELIX_SHELL_COMMAND_SERVICE_NAME) + .setCallbacks(&Cmp::addCShellCmd, &Cmp::removeCShellCmd) + .setRequired(false) + .setStrategy(DependencyUpdateStrategy::locking); -For more information examples please see + cmp.build(); // <--------------------------------------------------------------------------------------------<8> + } +}; -- [The C++ Dependency Manager API](../../libs/framework/include): The C++ dependency manager header files -- [Dm C++ example](../../examples/celix-examples/dm_example_cxx): A DM C++ example. -- [Getting Started: Using Services with C++](../../documents/getting_started/using_services_with_cxx.md): A introduction how to work with services using the C++ dependency manager +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithServiceDependencyActivator) +``` -## Using info +# When will a component be suspended -If the Celix C++ Dependency Manager is installed, 'find_package(Celix)' will set: -- The `Celix::shell_api` interface (i.e. headers only) library target -- The `Celix::shell` bundle target \ No newline at end of file +TODO explain that a component will be suspended with service dependency on suspend strategy, a matching service update +and if there is a svc inject callback configured. TODO check also start/stop method? diff --git a/examples/celix-examples/dm_example/phase1/src/phase1_activator.c b/examples/celix-examples/dm_example/phase1/src/phase1_activator.c index fd72d831..812d6af5 100644 --- a/examples/celix-examples/dm_example/phase1/src/phase1_activator.c +++ b/examples/celix-examples/dm_example/phase1/src/phase1_activator.c @@ -43,7 +43,9 @@ static celix_status_t activator_start(struct phase1_activator_struct *act, celix celix_dm_component_t *cmp= celix_dmComponent_create(ctx, "PHASE1_PROCESSING_COMPONENT"); celix_dmComponent_setImplementation(cmp, act->phase1Cmp); - CELIX_DMCOMPONENT_SETCALLBACKS(cmp, phase1_cmp_t *, phase1_init, phase1_start, phase1_stop, phase1_deinit); + CELIX_DM_COMPONENT_SET_CALLBACKS(cmp, phase1_cmp_t, phase1_init, phase1_start, phase1_stop, phase1_deinit); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(cmp, phase1_cmp_t, phase1_destroy); + phase1_setComp(act->phase1Cmp, cmp); celix_dmComponent_addInterface(cmp, PHASE1_NAME, PHASE1_VERSION, &act->phase1Serv, props); @@ -57,12 +59,6 @@ static celix_status_t activator_start(struct phase1_activator_struct *act, celix static celix_status_t activator_stop(struct phase1_activator_struct *act, celix_bundle_context_t *ctx) { printf("PHASE1: stop\n"); - celix_dependency_manager_t *mng = celix_bundleContext_getDependencyManager(ctx); - celix_dependencyManager_removeAllComponents(mng); - if (act->phase1Cmp != NULL) { - phase1_destroy(act->phase1Cmp); - } - return CELIX_SUCCESS; } diff --git a/examples/celix-examples/dm_example/phase2a/src/phase2a_activator.c b/examples/celix-examples/dm_example/phase2a/src/phase2a_activator.c index d92664b3..2107c3e9 100644 --- a/examples/celix-examples/dm_example/phase2a/src/phase2a_activator.c +++ b/examples/celix-examples/dm_example/phase2a/src/phase2a_activator.c @@ -45,9 +45,10 @@ static celix_status_t activator_start(struct phase2a_activator_struct *act, celi celix_dm_component_t *cmp = celix_dmComponent_create(ctx, "PHASE2A_PROCESSING_COMPONENT"); celix_dmComponent_setImplementation(cmp, act->phase2aCmp); - CELIX_DMCOMPONENT_SETCALLBACKS(cmp, phase2a_cmp_t *, phase2a_init, phase2a_start, phase2a_stop, phase2a_deinit); - celix_dmComponent_addInterface(cmp, PHASE2_NAME, PHASE2_VERSION, &act->phase2Serv, props); + CELIX_DM_COMPONENT_SET_CALLBACKS(cmp, phase2a_cmp_t, phase2a_init, phase2a_start, phase2a_stop, phase2a_deinit); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(cmp, phase2a_cmp_t, phase2a_destroy); + celix_dmComponent_addInterface(cmp, PHASE2_NAME, PHASE2_VERSION, &act->phase2Serv, props); celix_dm_service_dependency_t *dep = celix_dmServiceDependency_create(); celix_dmServiceDependency_setService(dep, PHASE1_NAME, PHASE1_RANGE_ALL, NULL); @@ -67,11 +68,6 @@ static celix_status_t activator_start(struct phase2a_activator_struct *act, celi static celix_status_t activator_stop(struct phase2a_activator_struct *act, celix_bundle_context_t *ctx) { printf("phase2a: stop\n"); - celix_dependency_manager_t *mng = celix_bundleContext_getDependencyManager(ctx); - celix_dependencyManager_removeAllComponents(mng); - if (act->phase2aCmp != NULL) { - phase2a_destroy(act->phase2aCmp); - } return CELIX_SUCCESS; } diff --git a/examples/celix-examples/dm_example/phase2b/src/phase2b_activator.c b/examples/celix-examples/dm_example/phase2b/src/phase2b_activator.c index c04ce045..cec241b6 100644 --- a/examples/celix-examples/dm_example/phase2b/src/phase2b_activator.c +++ b/examples/celix-examples/dm_example/phase2b/src/phase2b_activator.c @@ -45,7 +45,9 @@ static celix_status_t activator_start(struct phase2b_activator_struct *act, celi celix_dm_component_t *cmp = celix_dmComponent_create(ctx, "PHASE2B_PROCESSING_COMPONENT"); celix_dmComponent_setImplementation(cmp, act->phase2bCmp); - CELIX_DMCOMPONENT_SETCALLBACKS(cmp, phase2b_cmp_t *, phase2b_init, phase2b_start, phase2b_stop, phase2b_deinit); + CELIX_DM_COMPONENT_SET_CALLBACKS(cmp, phase2b_cmp_t, phase2b_init, phase2b_start, phase2b_stop, phase2b_deinit);\ + //note not configuring destroy callback -> destroying component manually in the activator stop callback. + celix_dmComponent_addInterface(cmp, PHASE2_NAME, PHASE2_VERSION, &act->phase2Serv, props); @@ -70,7 +72,7 @@ static celix_status_t activator_stop(struct phase2b_activator_struct *act, celix celix_dependency_manager_t *mng = celix_bundleContext_getDependencyManager(ctx); celix_dependencyManager_removeAllComponents(mng); if (act->phase2bCmp != NULL) { - phase2b_destroy(act->phase2bCmp); + phase2b_destroy(act->phase2bCmp); //destroy component implementation } return CELIX_SUCCESS; } diff --git a/examples/celix-examples/dm_example/phase3/src/phase3_activator.c b/examples/celix-examples/dm_example/phase3/src/phase3_activator.c index f699454f..77453c5c 100644 --- a/examples/celix-examples/dm_example/phase3/src/phase3_activator.c +++ b/examples/celix-examples/dm_example/phase3/src/phase3_activator.c @@ -36,7 +36,8 @@ static celix_status_t activator_start(struct phase3_activator_struct *act, celix celix_dm_component_t *cmp = celix_dmComponent_create(ctx, "PHASE3_PROCESSING_COMPONENT"); celix_dmComponent_setImplementation(cmp, act->phase3Cmp); - CELIX_DMCOMPONENT_SETCALLBACKS(cmp, phase3_cmp_t *, phase3_init, phase3_start, phase3_stop, phase3_deinit); + CELIX_DM_COMPONENT_SET_CALLBACKS(cmp, phase3_cmp_t, phase3_init, phase3_start, phase3_stop, phase3_deinit); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(cmp, phase3_cmp_t, phase3_destroy); celix_dm_service_dependency_t *dep = celix_dmServiceDependency_create(); celix_dmServiceDependency_setService(dep, PHASE2_NAME, NULL, NULL); @@ -57,16 +58,9 @@ static celix_status_t activator_start(struct phase3_activator_struct *act, celix } -static celix_status_t activator_stop(struct phase3_activator_struct *act, celix_bundle_context_t *ctx) { - celix_status_t status = CELIX_SUCCESS; +static celix_status_t activator_stop(struct phase3_activator_struct *act __attribute__((unused)), celix_bundle_context_t *ctx) { printf("phase3: stop\n"); - celix_dependency_manager_t *mng = celix_bundleContext_getDependencyManager(ctx); - celix_dependencyManager_removeAllComponents(mng); - if (act->phase3Cmp != NULL) { - phase3_destroy(act->phase3Cmp); - } - return status; + return CELIX_SUCCESS; } - -CELIX_GEN_BUNDLE_ACTIVATOR(struct phase3_activator_struct, activator_start, activator_stop); \ No newline at end of file +CELIX_GEN_BUNDLE_ACTIVATOR(struct phase3_activator_struct, activator_start, activator_stop); diff --git a/examples/celix-examples/readme_c_examples/CMakeLists.txt b/examples/celix-examples/readme_c_examples/CMakeLists.txt index 4ea4d406..2b7d3c39 100644 --- a/examples/celix-examples/readme_c_examples/CMakeLists.txt +++ b/examples/celix-examples/readme_c_examples/CMakeLists.txt @@ -53,6 +53,23 @@ add_celix_bundle(tracker_command_services_bundle ) target_link_libraries(tracker_command_services_bundle PRIVATE Celix::shell_api) +add_celix_bundle(simple_component_bundle + VERSION 1.0.0 + SOURCES src/simple_component_activator.c +) + +add_celix_bundle(component_with_provided_service_bundle + VERSION 1.0.0 + SOURCES src/component_with_provided_service_activator.c +) +target_link_libraries(component_with_provided_service_bundle PRIVATE Celix::shell_api) + +add_celix_bundle(component_with_service_dependency_bundle + VERSION 1.0.0 + SOURCES src/component_with_service_dependency_activator.c +) +target_link_libraries(component_with_service_dependency_bundle PRIVATE Celix::shell_api) + add_celix_container(readme_c_examples_container C BUNDLES @@ -61,4 +78,7 @@ add_celix_container(readme_c_examples_container my_shell_command_provider_bundle using_command_service_bundle tracker_command_services_bundle + simple_component_bundle + component_with_provided_service_bundle + component_with_service_dependency_bundle ) \ No newline at end of file diff --git a/examples/celix-examples/readme_c_examples/src/component_with_provided_service_activator.c b/examples/celix-examples/readme_c_examples/src/component_with_provided_service_activator.c new file mode 100644 index 00000000..5c79493b --- /dev/null +++ b/examples/celix-examples/readme_c_examples/src/component_with_provided_service_activator.c @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//src/component_with_provided_service_activator.c +#include <stdlib.h> +#include <celix_api.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 __attribute__((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) diff --git a/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c b/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c new file mode 100644 index 00000000..e44347e1 --- /dev/null +++ b/examples/celix-examples/readme_c_examples/src/component_with_service_dependency_activator.c @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//src/component_with_service_dependency_activator.c +#include <stdlib.h> +#include <celix_api.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; + 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); + 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; +} + +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); + 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(); + celix_dmServiceDependency_setService(dep1, CELIX_SHELL_COMMAND_SERVICE_NAME, NULL, NULL); + celix_dmServiceDependency_setStrategy(dep1, DM_SERVICE_DEPENDENCY_STRATEGY_SUSPEND); + celix_dmServiceDependency_setRequired(dep1, true); + celix_dm_service_dependency_callback_options_t opts1 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts1.set = (void*)componentWithServiceDependency_setHighestRankingShellCommand; + celix_dmServiceDependency_setCallbacksWithOptions(dep1, &opts1); + celix_dmComponent_addServiceDependency(dmCmp, dep1); + + //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); + celix_dmServiceDependency_setRequired(dep2, false); + celix_dm_service_dependency_callback_options_t opts2 = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts2.addWithProps = (void*)componentWithServiceDependency_addShellCommand; + 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) diff --git a/examples/celix-examples/readme_c_examples/src/simple_component_activator.c b/examples/celix-examples/readme_c_examples/src/simple_component_activator.c new file mode 100644 index 00000000..0c21fcff --- /dev/null +++ b/examples/celix-examples/readme_c_examples/src/simple_component_activator.c @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//src/simple_component_activator.c +#include <stdio.h> +#include <celix_api.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> \ No newline at end of file diff --git a/examples/celix-examples/readme_cxx_examples/CMakeLists.txt b/examples/celix-examples/readme_cxx_examples/CMakeLists.txt index 0548072c..14d7fa6a 100644 --- a/examples/celix-examples/readme_cxx_examples/CMakeLists.txt +++ b/examples/celix-examples/readme_cxx_examples/CMakeLists.txt @@ -115,12 +115,29 @@ add_celix_bundle(UsingCommandServicesExampleBundle target_link_libraries(UsingCommandServicesExampleBundle PRIVATE Celix::shell_api) add_celix_bundle(TrackingCommandServicesExampleBundle - VERSION 1.0.0 + VERSION 1.0.0 SOURCES src/TrackingCommandServicesExample.cc ) target_link_libraries(TrackingCommandServicesExampleBundle PRIVATE Celix::shell_api) +add_celix_bundle(SimpleComponentBundle + VERSION 1.0.0 + SOURCES src/SimpleComponentActivator.cc +) + +add_celix_bundle(ComponentWithProvidedServiceBundle + VERSION 1.0.0 + SOURCES src/ComponentWithProvidedServiceActivator.cc +) +target_link_libraries(ComponentWithProvidedServiceBundle PRIVATE Celix::shell_api) + +add_celix_bundle(ComponentWithServiceDependencyBundle + VERSION 1.0.0 + SOURCES src/ComponentWithServiceDependencyActivator.cc +) +target_link_libraries(ComponentWithServiceDependencyBundle PRIVATE Celix::shell_api) + add_celix_container(ReadmeCxxExamplesContainer BUNDLES Celix::ShellCxx @@ -129,6 +146,9 @@ add_celix_container(ReadmeCxxExamplesContainer MyCShellCommandProviderBundle UsingCommandServicesExampleBundle TrackingCommandServicesExampleBundle + SimpleComponentBundle + ComponentWithProvidedServiceBundle + ComponentWithServiceDependencyBundle ) if (TARGET my_shell_command_provider_bundle) diff --git a/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc new file mode 100644 index 00000000..0095a73a --- /dev/null +++ b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//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> + + 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.createProvidedCService(&shellCmd, CELIX_SHELL_COMMAND_SERVICE_NAME) + .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -----------------------------------------<7> + + cmp.build(); // <--------------------------------------------------------------------------------------------<8> + } +private: + celix_shell_command_t shellCmd{nullptr, nullptr}; +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithProvidedServiceActivator) diff --git a/examples/celix-examples/readme_cxx_examples/src/ComponentWithServiceDependencyActivator.cc b/examples/celix-examples/readme_cxx_examples/src/ComponentWithServiceDependencyActivator.cc new file mode 100644 index 00000000..1ba31500 --- /dev/null +++ b/examples/celix-examples/readme_cxx_examples/src/ComponentWithServiceDependencyActivator.cc @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +//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; + } + + 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}; + 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 below + 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>(); // <-------------<1> + + cmp.createServiceDependency<celix::IShellCommand>() + .setCallbacks(&Cmp::setHighestRankingShellCommand) + .setRequired(true) + .setStrategy(DependencyUpdateStrategy::suspend); + + cmp.createServiceDependency<celix_shell_command_t>(CELIX_SHELL_COMMAND_SERVICE_NAME) + .setCallbacks(&Cmp::addCShellCmd, &Cmp::removeCShellCmd) + .setRequired(false) + .setStrategy(DependencyUpdateStrategy::locking); + + cmp.build(); // <--------------------------------------------------------------------------------------------<8> + } +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithServiceDependencyActivator) diff --git a/examples/celix-examples/readme_cxx_examples/src/SimpleComponentActivator.cc b/examples/celix-examples/readme_cxx_examples/src/SimpleComponentActivator.cc new file mode 100644 index 00000000..d1cfcd6c --- /dev/null +++ b/examples/celix-examples/readme_cxx_examples/src/SimpleComponentActivator.cc @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <celix/BundleActivator.h> + +class SimpleComponent { +public: + void init() { + std::cout << "Initializing SimpleComponent. Transition nr " << transitionCount++ << std::endl; + } + + void start() { + std::cout << "starting SimpleComponent. Transition nr " << transitionCount++ << std::endl; + } + + void stop() { + std::cout << "Stopping SimpleComponent. Transition nr " << transitionCount++ << std::endl; + } + + void deinit() { + std::cout << "De-initializing SimpleComponent. 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>(); + ctx->getDependencyManager()->createComponent(std::move(cmp), "SimpleComponent1") + .setCallbacks( + &SimpleComponent::init, + &SimpleComponent::start, + &SimpleComponent::stop, + &SimpleComponent::deinit) + .build(); + } +}; + +CELIX_GEN_CXX_BUNDLE_ACTIVATOR(SimpleComponentActivator) diff --git a/libs/framework/gtest/src/DependencyManagerTestSuite.cc b/libs/framework/gtest/src/DependencyManagerTestSuite.cc index 5cb65b0b..db217ad9 100644 --- a/libs/framework/gtest/src/DependencyManagerTestSuite.cc +++ b/libs/framework/gtest/src/DependencyManagerTestSuite.cc @@ -86,6 +86,43 @@ TEST_F(DependencyManagerTestSuite, DmComponentAddRemove) { ASSERT_EQ(0, celix_dependencyManager_nrOfComponents(mng)); } +TEST_F(DependencyManagerTestSuite, DmComponentsWithConfiguredDestroyFunction) { + //Given a simple component implementation (only a name field) + struct CmpImpl { + std::string name; + }; + + void(*destroyFn)(CmpImpl*) = [](CmpImpl* impl) { + std::cout << "Destroying " << impl->name << std::endl; + delete impl; + }; + + //When 10 of these components impls are created and configured (include destroy impl function) + //as component in the DM. + auto* dm = celix_bundleContext_getDependencyManager(ctx); + for (int i = 0; i < 10; ++i) { + auto* impl = new CmpImpl{std::string{"Component"} + std::to_string(i+1)}; + auto* dmCmp = celix_dmComponent_create(ctx, impl->name.c_str()); + celix_dmComponent_setImplementation(dmCmp, impl); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(dmCmp, CmpImpl, destroyFn); + celix_dependencyManager_addAsync(dm, dmCmp); + } + + //Then all component should become activate (note components with no svc dependencies) + celix_dependencyManager_wait(dm); + celix_dependencyManager_allComponentsActive(dm); + EXPECT_EQ(celix_dependencyManager_nrOfComponents(dm), 10); + + //When all 10 components are removed + celix_dependencyManager_removeAllComponents(dm); + + //Then all components should be removed + EXPECT_EQ(celix_dependencyManager_nrOfComponents(dm), 0); + + //And when the test goes out of scope, no memory should be leaked + //nop +} + TEST_F(DependencyManagerTestSuite, DmComponentAddRemoveAsync) { auto *mng = celix_bundleContext_getDependencyManager(ctx); diff --git a/libs/framework/include/celix_bundle_activator.h b/libs/framework/include/celix_bundle_activator.h index bab054c3..19dc1770 100644 --- a/libs/framework/include/celix_bundle_activator.h +++ b/libs/framework/include/celix_bundle_activator.h @@ -20,6 +20,7 @@ #include <stdlib.h> #include "celix_bundle_context.h" +#include "celix_dependency_manager.h" #ifndef CELIX_BUNDLE_ACTIVATOR_H_ #define CELIX_BUNDLE_ACTIVATOR_H_ @@ -103,11 +104,6 @@ celix_status_t celix_bundleActivator_stop(void *userData, celix_bundle_context_t */ celix_status_t celix_bundleActivator_destroy(void *userData, celix_bundle_context_t* ctx); -/** - * @brief Returns the C bundle context. - */ -celix_bundle_context_t* celix_bundleActivator_getBundleContext(); - /** * @brief This macro generates the required bundle activator functions for C. * @@ -118,9 +114,16 @@ celix_bundle_context_t* celix_bundleActivator_getBundleContext(); * - bundleActivator_start/stop which will call the respectively provided typed start/stop functions. * - bundleActivator_destroy will free the allocated for the provided type. * - * @param type The activator type (e.g. 'struct shell_activator'). - * @param start the activator actStart function with the following signature: celix_status_t (*)(<actType>*, celix_bundle_context_t*). - * @param stop the activator actStop function with the following signature: celix_status_t (*)(<actType>*, celix_bundle_context_t*). + * After the (optional) provided activator stop callback is called, the generated `celix_bundleActivator_stop` + * function will remove all components from the bundle's dependency manager + * (using `celix_dependencyManager_removeAllComponents). This will ensure that dependency manager components can be + * used without explicitly programming their removal and destroy functionality. + * + * @param actType The activator type (e.g. 'struct shell_activator'). + * @param actStart The optional activator actStart function with the following signature: + * celix_status_t (*)(<actType>*, celix_bundle_context_t*). + * @param actStop The optional activator actStop function with the following signature: + * celix_status_t (*)(<actType>*, celix_bundle_context_t*). */ #define CELIX_GEN_BUNDLE_ACTIVATOR(actType, actStart, actStop) \ \ @@ -136,11 +139,23 @@ celix_status_t celix_bundleActivator_create(celix_bundle_context_t *ctx, void ** } \ \ celix_status_t celix_bundleActivator_start(void *userData, celix_bundle_context_t *ctx) { \ - return actStart((actType*)userData, ctx); \ + celix_status_t status = CELIX_SUCCESS; \ + celix_status_t (*fn)(actType*, celix_bundle_context_t*) = (actStart); \ + if (fn != NULL) { \ + status = fn((actType*)userData, ctx); \ + } \ + return status; \ } \ \ celix_status_t celix_bundleActivator_stop(void *userData, celix_bundle_context_t *ctx) { \ - return actStop((actType*)userData, ctx); \ + celix_status_t status = CELIX_SUCCESS; \ + celix_status_t (*fn)(actType*, celix_bundle_context_t*) = (actStop); \ + if (fn != NULL) { \ + status = fn((actType*)userData, ctx); \ + } \ + celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(ctx); \ + celix_dependencyManager_removeAllComponents(mng); \ + return status; \ } \ \ celix_status_t celix_bundleActivator_destroy(void *userData, celix_bundle_context_t *ctx) { \ diff --git a/libs/framework/include/celix_dependency_manager.h b/libs/framework/include/celix_dependency_manager.h index d4cf37a5..8e369375 100644 --- a/libs/framework/include/celix_dependency_manager.h +++ b/libs/framework/include/celix_dependency_manager.h @@ -45,9 +45,12 @@ extern "C" { /** * Adds a DM component to the dependency manager * - * After this call the components will be created and if the components can be started, they + * After this call the components will be build and if the components can be started, they * will be started and the services will be registered. * + * After this call the dependency manager has ownership of the component and the dependency manager will also + * de-active and destroy the component when the dependency manager is destroyed. + * * Should not be called from the Celix event thread. */ celix_status_t celix_dependencyManager_add(celix_dependency_manager_t *manager, celix_dm_component_t *component); @@ -62,16 +65,15 @@ celix_status_t celix_dependencyManager_addAsync(celix_dependency_manager_t *mana /** * Removes a DM component from the dependency manager and destroys it * - * After this call the components will be destroyed and if the components was started, the service registrations - * and service tracked of this component will be unregistered and closed. + * After this call - and if the component was found - the component will be destroyed and if the component was started, + * the component will have been stopped and de-initialized. * * Should not be called from the Celix event thread. */ celix_status_t celix_dependencyManager_remove(celix_dependency_manager_t *manager, celix_dm_component_t *component); /** - * Same as celix_dependencyManager_remove, but this call will not wait until all service registrations and - * tracker are unregistered/closed on the Celix event thread. + * Same as celix_dependencyManager_remove, but this call will not wait until component is deactivated. * Can be called on the Celix event thread. * * The doneCallback will be called (if not NULL) with doneData as argument when the component is removed diff --git a/libs/framework/include/celix_dm_component.h b/libs/framework/include/celix_dm_component.h index 8302dca0..6f0b0d5a 100644 --- a/libs/framework/include/celix_dm_component.h +++ b/libs/framework/include/celix_dm_component.h @@ -61,6 +61,7 @@ typedef enum celix_dm_component_state_enum { #define CELIX_DM_COMPONENT_MAX_NAME_LENGTH 128 typedef int (*celix_dm_cmp_lifecycle_fpt)(void *userData); +typedef void (*celix_dm_cmp_impl_destroy_fpt)(void*); /** * Creates a DM Component with a random generated UUID. @@ -82,14 +83,14 @@ const char* celix_dmComponent_getUUID(celix_dm_component_t* cmp); /** * Destroys a DM Component */ -void celix_dmComponent_destroy(celix_dm_component_t *cmp); +void celix_dmComponent_destroy(celix_dm_component_t* cmp); /** * Destroys a DM Component on the event thread. * Will call doneCallback (if not NULL) when done. * */ -void celix_dmComponent_destroyAsync(celix_dm_component_t *cmp, void *doneData, void (*doneCallback)(void*)); +void celix_dmComponent_destroyAsync(celix_dm_component_t* cmp, void *doneData, void (*doneCallback)(void*)); /** * Specify if a default 'service.lang=C' should be added to the properties of interfaces if no 'service.lang' has been @@ -119,6 +120,26 @@ celix_status_t celix_dmComponent_removeInterface(celix_dm_component_t *component */ celix_status_t celix_dmComponent_setImplementation(celix_dm_component_t *component, void* implementation); +/** + * Configures the destroy function for the component implementation. + * + * If a destroy function for the component implementation is configured, this will be used + * when the component is removed from the dependency manager and component is successfully de-activated. + * + * The destroy function will not be called if the component implementation is not set. e.g. if the + * celix_dmComponent_setImplementation is not called with a non NULL value. + */ +void celix_dmComponent_setImplementationDestroyFunction(celix_dm_component_t* cmp, celix_dm_cmp_impl_destroy_fpt destroyFn); + +/** + * Configures the destroy function for the component implementation using a MACRO for improving the type safety. + */ +#define CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(dmCmp, type, destroy) \ + do { \ + void (*_destroyFunction)(type*) = (destroy); \ + celix_dmComponent_setImplementationDestroyFunction((dmCmp), (void(*)(void*))_destroyFunction); \ + } while(0) + /** * Returns an arraylist of service names. The caller owns the arraylist and strings (char *) */ @@ -137,17 +158,22 @@ celix_status_t celix_dmComponent_removeServiceDependency(celix_dm_component_t *c /** * Returns the current state of the component. */ -celix_dm_component_state_t celix_dmComponent_currentState(celix_dm_component_t *cmp); +celix_dm_component_state_t celix_dmComponent_currentState(celix_dm_component_t* cmp); /** * Returns the implementation of the component. e.g. the component handle/self/this pointer. */ -void * celix_dmComponent_getImplementation(celix_dm_component_t *cmp); +void * celix_dmComponent_getImplementation(celix_dm_component_t* cmp); + +/** + * Returns the configured component implementation destroy function. + */ +celix_dm_cmp_impl_destroy_fpt celix_dmComponent_getImplementationDestroyFunction(celix_dm_component_t* cmp); /** * Returns the DM component name. This is used when printing information about the component. */ -const char* celix_dmComponent_getName(celix_dm_component_t *cmp); +const char* celix_dmComponent_getName(celix_dm_component_t* cmp); /** * Returns bundle context for the bundle where this DM component is part of. @@ -163,15 +189,22 @@ celix_status_t celix_dmComponent_setCallbacks(celix_dm_component_t *component, c /** * Set the component life cycle callbacks using a MACRO for improving the type safety. */ -#define CELIX_DMCOMPONENT_SETCALLBACKS(dmCmp, type, init, start, stop, deinit) \ +#define CELIX_DM_COMPONENT_SET_CALLBACKS(dmCmp, type, init, start, stop, deinit) \ do { \ - int (*tmp_init)(type) = (init); \ - int (*tmp_start)(type) = (start); \ - int (*tmp_stop)(type) = (stop); \ - int (*tmp_deinit)(type) = (deinit); \ - celix_dmComponent_setCallbacks((dmCmp), (celix_dm_cmp_lifecycle_fpt)tmp_init, (celix_dm_cmp_lifecycle_fpt)tmp_start, (celix_dm_cmp_lifecycle_fpt)tmp_stop, (celix_dm_cmp_lifecycle_fpt)tmp_deinit); \ + int (*_tmp_init)(type*) = (init); \ + int (*_tmp_start)(type*) = (start); \ + int (*_tmp_stop)(type*) = (stop); \ + int (*_tmp_deinit)(type*) = (deinit); \ + celix_dmComponent_setCallbacks((dmCmp), (celix_dm_cmp_lifecycle_fpt)_tmp_init, (celix_dm_cmp_lifecycle_fpt)_tmp_start, (celix_dm_cmp_lifecycle_fpt)_tmp_stop, (celix_dm_cmp_lifecycle_fpt)_tmp_deinit); \ } while(0) +/** + * Deprecated, use CELIX_DM_COMPONENT_SET_CALLBACKS instead. + */ + +#define CELIX_DMCOMPONENT_SETCALLBACKS(dmCmp, type, init, start, stop, deinit) \ + CELIX_DM_COMPONENT_SET_CALLBACKS(dmCmp, type*, init, start, stop, deinit) + /** * Create a DM Component info struct. Containing information about the component. * Caller has ownership. diff --git a/libs/framework/src/dm_component_impl.c b/libs/framework/src/dm_component_impl.c index a951efc1..befa81bb 100644 --- a/libs/framework/src/dm_component_impl.c +++ b/libs/framework/src/dm_component_impl.c @@ -35,6 +35,7 @@ struct celix_dm_component_struct { celix_bundle_context_t* context; void* implementation; + celix_dm_cmp_impl_destroy_fpt implementationDestroyFn; celix_dm_cmp_lifecycle_fpt callbackInit; celix_dm_cmp_lifecycle_fpt callbackStart; @@ -130,6 +131,10 @@ celix_dm_component_t* celix_dmComponent_createWithUUID(bundle_context_t *context component->callbackStart = NULL; component->callbackStop = NULL; component->callbackDeinit = NULL; + + component->implementation= NULL; + component->implementationDestroyFn = NULL; + component->state = CELIX_DM_CMP_STATE_INACTIVE; component->providedInterfaces = celix_arrayList_create(); @@ -178,8 +183,21 @@ struct celix_dm_component_destroy_data { static void celix_dmComponent_destroyCallback(void *voidData) { struct celix_dm_component_destroy_data *data = voidData; celix_dm_component_t *component = data->cmp; - celix_dmComponent_disable(component); //all service deregistered // all svc tracker stopped + celix_dmComponent_disable(component); //all service unregistered // all svc tracker stopped if (celix_dmComponent_isDisabled(component)) { + if (component->implementationDestroyFn) { + if (component->implementation == NULL) { + celix_bundleContext_log(component->context, CELIX_LOG_LEVEL_ERROR, + "Component `%s` [uuid=%s] has a configured destroy component " + "implementation callback, but the implementation pointer is NULL. " + "Destroy callback will not be called!", + component->name, + component->uuid); + } else { + component->implementationDestroyFn(component->implementation); + } + } + for (int i = 0; i < celix_arrayList_size(component->providedInterfaces); ++i) { dm_interface_t *interface = celix_arrayList_get(component->providedInterfaces, i); @@ -975,6 +993,10 @@ celix_status_t celix_dmComponent_setImplementation(celix_dm_component_t *compone return CELIX_SUCCESS; } +void celix_dmComponent_setImplementationDestroyFunction(celix_dm_component_t* component, celix_dm_cmp_impl_destroy_fpt destroyFn) { + component->implementationDestroyFn = destroyFn; +} + celix_status_t component_getBundleContext(celix_dm_component_t *component, bundle_context_pt *context) { *context = celix_dmComponent_getBundleContext(component); return *context == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS;
