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) {
}