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 <[email protected]>
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.

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