You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2022/05/23 20:19:59 UTC

[celix] branch feature/update_component_and_pattern_documentation updated: Update dm component document and examples.

This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/update_component_and_pattern_documentation
in repository https://gitbox.apache.org/repos/asf/celix.git


The following commit(s) were added to refs/heads/feature/update_component_and_pattern_documentation by this push:
     new a1aa7a11 Update dm component document and examples.
a1aa7a11 is described below

commit a1aa7a11459e98872352318dbd4dc1089d39e6a9
Author: Pepijn Noltes <pe...@gmail.com>
AuthorDate: Mon May 23 22:19:50 2022 +0200

    Update dm component document and examples.
    
    Also adds support to provide an unassociated service as shared_ptr in the C++
    component api.
---
 documents/components.md                            | 51 ++++++++++++----------
 .../src/ComponentWithProvidedServiceActivator.cc   |  8 ++--
 .../gtest/src/DependencyManagerTestSuite.cc        | 37 ++++++++++++++++
 libs/framework/include/celix/dm/Component.h        | 25 +++++++++--
 libs/framework/include/celix/dm/Component_Impl.h   | 43 +++++++++++++-----
 libs/framework/include/celix/dm/ProvidedService.h  |  6 +--
 .../include/celix/dm/ProvidedService_Impl.h        | 12 ++---
 7 files changed, 132 insertions(+), 50 deletions(-)

diff --git a/documents/components.md b/documents/components.md
index 5f8c96fb..86e2986a 100644
--- a/documents/components.md
+++ b/documents/components.md
@@ -19,39 +19,32 @@ See the License for the specific language governing permissions and
 limitations under the License.
 -->
 
-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.
-
 # 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.
+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.   
+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.
+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. 
+Each DM Component has its own lifecycle. 
 A component's lifecycle state model is depicted in the state diagram below.
 
 ![Component Life Cycle](diagrams/component_lifecycle.png)
 
-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:
+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`.
+   
+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 DM Component has the following lifecycle states:
 - `Inactive`: _The component is inactive and the DM is not managing the component yet._
@@ -76,6 +69,11 @@ A DM Component has the following lifecycle states:
   provided service and calling the `stop` callback._
 - `Deinitializing`: _The component is being removed and is deinitializing: 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
@@ -357,8 +355,8 @@ Remarks for the C++ example:
 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.
+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 if the component enters the state
    `Tracking Optional`..
 8. "Build" the component so the DM will manage and try to activate the component. 
@@ -396,21 +394,22 @@ public:
         auto& cmp = ctx->getDependencyManager()->createComponent<ComponentWithProvidedService>(); // <---------------<4>
 
         cmp.createProvidedService<celix::IShellCommand>()
-            .addProperty(celix::IShellCommand::COMMAND_NAME, "HelloComponent"); // <---------------------------------<5>
+                .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 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.createProvidedCService(&shellCmd, CELIX_SHELL_COMMAND_SERVICE_NAME)
-            .addProperty(CELIX_SHELL_COMMAND_NAME, "hello_component"); // < -----------------------------------------<7>
+
+        cmp.createUnassociatedProvidedService(std::move(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)
@@ -642,3 +641,7 @@ CELIX_GEN_CXX_BUNDLE_ACTIVATOR(ComponentWithServiceDependencyActivator)
 
 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?
+
+# TODOs
+
+overview of function (e.g. setFilter for dependencies, etc)
\ No newline at end of file
diff --git a/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
index 0095a73a..31b9758e 100644
--- a/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
+++ b/examples/celix-examples/readme_cxx_examples/src/ComponentWithProvidedServiceActivator.cc
@@ -50,20 +50,20 @@ public:
         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 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.createProvidedCService(&shellCmd, CELIX_SHELL_COMMAND_SERVICE_NAME)
+        cmp.createUnassociatedProvidedService(std::move(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/libs/framework/gtest/src/DependencyManagerTestSuite.cc b/libs/framework/gtest/src/DependencyManagerTestSuite.cc
index 8a2ca989..3e0f5d5e 100644
--- a/libs/framework/gtest/src/DependencyManagerTestSuite.cc
+++ b/libs/framework/gtest/src/DependencyManagerTestSuite.cc
@@ -222,6 +222,10 @@ public:
     }
 };
 
+class Cmp3 /*note no inherit*/ {
+
+};
+
 
 TEST_F(DependencyManagerTestSuite, CxxDmGetInfo) {
     celix::dm::DependencyManager mng{ctx};
@@ -389,6 +393,39 @@ TEST_F(DependencyManagerTestSuite, BuildSvcProvide) {
     EXPECT_EQ(svcId, -1); //cleared -> not found
 }
 
+TEST_F(DependencyManagerTestSuite, BuildUnassociatedProvidedService) {
+    celix::dm::DependencyManager dm{ctx};
+
+    //Given a component which does not inherit any interfaces
+    auto& cmp = dm.createComponent<Cmp1>(std::make_shared<Cmp1>());
+
+    //Then I can create a provided service using a shared_ptr which is not associated with the component type
+    // (TestService is not a base of Cmp3)
+    cmp.createUnassociatedProvidedService(std::make_shared<TestService>())
+        .addProperty("test1", "value1");
+
+    //And I can create a provided service using a shared_ptr and a custom name
+    cmp.createUnassociatedProvidedService(std::make_shared<TestService>(), "CustomName");
+
+    //When I build the component
+    cmp.build();
+
+    //Then the nr of component is 1
+    ASSERT_EQ(1, dm.getNrOfComponents()); //cmp "build", so active
+
+    //And the nr of provided interfaces of that component is 2
+    auto info = dm.getInfo();
+    ASSERT_EQ(info.components[0].interfacesInfo.size(), 2);
+
+    //And the first (index 0) provided service has a name TestService and a property test1 with value value1
+    EXPECT_STREQ(info.components[0].interfacesInfo[0].serviceName.c_str(), "TestService");
+    EXPECT_STREQ(info.components[0].interfacesInfo[0].properties["test1"].c_str(), "value1");
+
+    //And the second (index 1) provide service has a name "CustomName".
+    EXPECT_STREQ(info.components[0].interfacesInfo[1].serviceName.c_str(), "CustomName");
+}
+
+
 TEST_F(DependencyManagerTestSuite, AddSvcDepAfterBuild) {
     celix::dm::DependencyManager dm{ctx};
     EXPECT_EQ(0, dm.getNrOfComponents());
diff --git a/libs/framework/include/celix/dm/Component.h b/libs/framework/include/celix/dm/Component.h
index d101b35f..199190b8 100644
--- a/libs/framework/include/celix/dm/Component.h
+++ b/libs/framework/include/celix/dm/Component.h
@@ -258,20 +258,39 @@ namespace celix { namespace dm {
 
 
         /**
-         * Creates a provided C services. The provided service can be fine tuned and build using a fluent API
+         * @brief Creates a provided C services the component.
+         *
+         * The provided service can be fine tuned and build using a fluent API
+         *
          * @param svc  The pointer to a C service (c struct)
          * @param serviceName The service name to use
          */
         template<class I> ProvidedService<T,I>& createProvidedCService(I* svc, std::string serviceName);
 
         /**
-         * Creates a provided C++ services. The provided service can be fine tuned and build using a fluent API
-         * The service pointer is based on the component instance.
+         * @brief Creates a provided C++ services for the component.
+         *
+         * The provided service can be fine tuned and build using a fluent API
+         *
+         * @note The service type I must be a base of component type T.
          *
          * @param serviceName The optional service name. If not provided the service name is inferred from I.
          */
         template<class I> ProvidedService<T,I>& createProvidedService(std::string serviceName = {});
 
+        /**
+         * @brief Creates a unassociated provided services for the component.
+         *
+         * The provided service can be fine tuned and build using a fluent API
+         *
+         * @note The provided service can - and is expected to be - be unassociated with the component type.
+         * I.e. it can be a C service.
+         * The ProvidedService result will store the shared_ptr of the service during its lifecycle.
+         *
+         * @param serviceName The optional service name. If not provided the service name is inferred from I.
+         */
+        template<class I> ProvidedService<T,I>& createUnassociatedProvidedService(std::shared_ptr<I> svc, std::string serviceName = {});
+
         /**
          * Adds a C interface to provide as service to the Celix framework.
          *
diff --git a/libs/framework/include/celix/dm/Component_Impl.h b/libs/framework/include/celix/dm/Component_Impl.h
index d860afa2..f8a79f5b 100644
--- a/libs/framework/include/celix/dm/Component_Impl.h
+++ b/libs/framework/include/celix/dm/Component_Impl.h
@@ -68,11 +68,12 @@ Component<T>::~Component() = default;
 template<class T>
 template<class I>
 Component<T>& Component<T>::addInterfaceWithName(const std::string &serviceName, const std::string &version, const Properties &properties) {
+    static_assert(std::is_base_of<I,T>::value, "Component T must implement Interface I");
     if (!serviceName.empty()) {
         T* cmpPtr = &this->getInstance();
-        I* intfPtr = static_cast<I*>(cmpPtr); //NOTE T should implement I
-
-        auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, intfPtr, true);
+        I* svcPtr = static_cast<I*>(cmpPtr); //NOTE T should implement I
+        std::shared_ptr<I> svc{svcPtr, [](I*){/*nop*/}};
+        auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, std::move(svc), true);
         provide->setVersion(version);
         provide->setProperties(properties);
         std::lock_guard<std::mutex> lck{mutex};
@@ -87,7 +88,6 @@ Component<T>& Component<T>::addInterfaceWithName(const std::string &serviceName,
 template<class T>
 template<class I>
 Component<T>& Component<T>::addInterface(const std::string &version, const Properties &properties) {
-    //get name if not provided
     static_assert(std::is_base_of<I,T>::value, "Component T must implement Interface I");
     std::string serviceName = typeName<I>();
     if (serviceName.empty()) {
@@ -103,8 +103,9 @@ Component<T>& Component<T>::addInterface(const std::string &version, const Prope
 
 template<class T>
 template<class I>
-Component<T>& Component<T>::addCInterface(I* svc, const std::string &serviceName, const std::string &version, const Properties &properties) {
-    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, svc, false);
+Component<T>& Component<T>::addCInterface(I* svcPtr, const std::string &serviceName, const std::string &version, const Properties &properties) {
+    std::shared_ptr<I> svc{svcPtr, [](I*){/*nop*/}};
+    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, std::move(svc), false);
     provide->setVersion(version);
     provide->setProperties(properties);
     std::lock_guard<std::mutex> lck{mutex};
@@ -389,8 +390,9 @@ Component<T>& Component<T>::buildAsync() {
 
 template<class T>
 template<class I>
-ProvidedService<T, I> &Component<T>::createProvidedCService(I *svc, std::string serviceName) {
-    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, svc, false);
+ProvidedService<T, I> &Component<T>::createProvidedCService(I *svcPtr, std::string serviceName) {
+    std::shared_ptr<I> svc{svcPtr, [](I*){/*nop*/}};
+    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, std::move(svc), false);
     std::lock_guard<std::mutex> lck{mutex};
     providedServices.push_back(provide);
     return *provide;
@@ -398,7 +400,7 @@ ProvidedService<T, I> &Component<T>::createProvidedCService(I *svc, std::string
 
 template<class T>
 template<class I>
-ProvidedService<T, I> &Component<T>::createProvidedService(std::string serviceName) {
+ProvidedService<T, I>& Component<T>::createProvidedService(std::string serviceName) {
     static_assert(std::is_base_of<I,T>::value, "Component T must implement Interface I");
     if (serviceName.empty()) {
         serviceName = typeName<I>();
@@ -408,7 +410,28 @@ ProvidedService<T, I> &Component<T>::createProvidedService(std::string serviceNa
     }
 
     I* svcPtr = &this->getInstance();
-    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, svcPtr, true);
+    std::shared_ptr<I> svc{svcPtr, [](I*){/*nop*/}};
+    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, std::move(svc), true);
+    auto svcVersion = celix::typeVersion<I>();
+    if (!svcVersion.empty()) {
+        provide->addProperty(celix::SERVICE_VERSION, std::move(svcVersion));
+    }
+    std::lock_guard<std::mutex> lck{mutex};
+    providedServices.push_back(provide);
+    return *provide;
+}
+
+template<class T>
+template<class I>
+ProvidedService<T, I>& Component<T>::createUnassociatedProvidedService(std::shared_ptr<I> svc, std::string serviceName) {
+    if (serviceName.empty()) {
+        serviceName = typeName<I>();
+    }
+    if (serviceName.empty()) {
+        std::cerr << "Cannot add interface, because type name could not be inferred. function: '"  << __PRETTY_FUNCTION__ << "'\n";
+    }
+
+    auto provide = std::make_shared<ProvidedService<T,I>>(cComponent(), serviceName, std::move(svc), true);
     auto svcVersion = celix::typeVersion<I>();
     if (!svcVersion.empty()) {
         provide->addProperty(celix::SERVICE_VERSION, std::move(svcVersion));
diff --git a/libs/framework/include/celix/dm/ProvidedService.h b/libs/framework/include/celix/dm/ProvidedService.h
index 423f981c..5f458ad0 100644
--- a/libs/framework/include/celix/dm/ProvidedService.h
+++ b/libs/framework/include/celix/dm/ProvidedService.h
@@ -28,7 +28,7 @@ namespace celix { namespace dm {
 
     class BaseProvidedService {
     public:
-        BaseProvidedService(celix_dm_component_t* _cmp, std::string svcName, void* svc, bool _cppService);
+        BaseProvidedService(celix_dm_component_t* _cmp, std::string svcName, std::shared_ptr<void> svc, bool _cppService);
 
         BaseProvidedService(BaseProvidedService&&) = delete;
         BaseProvidedService& operator=(BaseProvidedService&&) = delete;
@@ -46,7 +46,7 @@ namespace celix { namespace dm {
     protected:
         celix_dm_component_t* cCmp;
         std::string svcName;
-        void* svcPointer;
+        std::shared_ptr<void> svc;
         bool cppService;
         std::string svcVersion{};
         celix::dm::Properties properties{};
@@ -56,7 +56,7 @@ namespace celix { namespace dm {
     template<typename T, typename I>
     class ProvidedService : public BaseProvidedService {
     public:
-        ProvidedService(celix_dm_component_t* _cmp, std::string svcName, I* svc, bool _cppService);
+        ProvidedService(celix_dm_component_t* _cmp, std::string svcName, std::shared_ptr<I> svc, bool _cppService);
 
         /**
          * Set the version of the interface
diff --git a/libs/framework/include/celix/dm/ProvidedService_Impl.h b/libs/framework/include/celix/dm/ProvidedService_Impl.h
index ff11911c..3622272c 100644
--- a/libs/framework/include/celix/dm/ProvidedService_Impl.h
+++ b/libs/framework/include/celix/dm/ProvidedService_Impl.h
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-inline celix::dm::BaseProvidedService::BaseProvidedService(celix_dm_component_t* _cmp, std::string _svcName, void* _svc, bool _cppService) : cCmp{_cmp}, svcName{std::move(_svcName)}, svcPointer{_svc}, cppService{_cppService} {}
+inline celix::dm::BaseProvidedService::BaseProvidedService(celix_dm_component_t* _cmp, std::string _svcName, std::shared_ptr<void> _svc, bool _cppService) : cCmp{_cmp}, svcName{std::move(_svcName)}, svc{std::move(_svc)}, cppService{_cppService} {}
 
 inline const std::string &BaseProvidedService::getName() const {
     return svcName;
@@ -31,8 +31,8 @@ inline bool BaseProvidedService::isCppService() const {
     return cppService;
 }
 
-inline void *BaseProvidedService::getService() const {
-    return svcPointer;
+inline void* BaseProvidedService::getService() const {
+    return svc.get();
 }
 
 inline const celix::dm::Properties &BaseProvidedService::getProperties() const {
@@ -48,7 +48,7 @@ inline void BaseProvidedService::runBuild() {
         }
 
         const char *cVersion = svcVersion.empty() ? nullptr : svcVersion.c_str();
-        celix_dmComponent_addInterface(cCmp, svcName.c_str(), cVersion, svcPointer, cProperties);
+        celix_dmComponent_addInterface(cCmp, svcName.c_str(), cVersion, svc.get(), cProperties);
     }
     provideAddedToCmp = true;
 }
@@ -100,8 +100,8 @@ ProvidedService<T, I> &ProvidedService<T, I>::buildAsync() {
 }
 
 template<typename T, typename I>
-ProvidedService<T, I>::ProvidedService(celix_dm_component_t *_cmp, std::string svcName, I* _svc, bool _cppService)
-        :BaseProvidedService(_cmp, svcName, static_cast<void*>(_svc), _cppService) {
+ProvidedService<T, I>::ProvidedService(celix_dm_component_t *_cmp, std::string svcName, std::shared_ptr<I> _svc, bool _cppService)
+        :BaseProvidedService(_cmp, svcName, std::static_pointer_cast<void>(std::move(_svc)), _cppService) {
 
 }