This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/551-add-1st-part-req-cap-model-lib in repository https://gitbox.apache.org/repos/asf/celix.git
commit 6268ea704a66082f450970afcda5b95580fe2851 Author: Pepijn Noltes <[email protected]> AuthorDate: Sun May 7 14:09:10 2023 +0200 #551: Add 1st part of the requirement-capability-model lib --- libs/CMakeLists.txt | 1 + libs/{ => rcm}/CMakeLists.txt | 41 ++-- libs/rcm/README.md | 51 ++++ libs/rcm/diagrams/logical-req-cap-model.png | Bin 0 -> 4927 bytes libs/rcm/diagrams/logical-req-cap-model.puml | 23 ++ libs/rcm/gtest/CMakeLists.txt | 38 +++ .../src/RequirementCapabilityModelTestSuite.cc | 263 +++++++++++++++++++++ ...ntCapabilityModelWithErrorInjectionTestSuite.cc | 133 +++++++++++ libs/rcm/include/celix_capability.h | 161 +++++++++++++ libs/rcm/include/celix_rcm_types.h | 42 ++++ libs/rcm/include/celix_requirement.h | 173 ++++++++++++++ libs/rcm/include/celix_resource.h | 112 +++++++++ libs/rcm/src/celix_capability.c | 172 ++++++++++++++ libs/rcm/src/celix_requirement.c | 185 +++++++++++++++ libs/rcm/src/celix_resource.c | 155 ++++++++++++ 15 files changed, 1528 insertions(+), 22 deletions(-) diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index feee84b7..cb8481a2 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -18,6 +18,7 @@ #utils, dfi and etcdlib are standalone #(e.g. no dependency on celix framework add_subdirectory(utils) +add_subdirectory(rcm) add_subdirectory(dfi) add_subdirectory(etcdlib) add_subdirectory(promises) diff --git a/libs/CMakeLists.txt b/libs/rcm/CMakeLists.txt similarity index 51% copy from libs/CMakeLists.txt copy to libs/rcm/CMakeLists.txt index feee84b7..23e42266 100644 --- a/libs/CMakeLists.txt +++ b/libs/rcm/CMakeLists.txt @@ -5,9 +5,9 @@ # 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 @@ -15,27 +15,24 @@ # specific language governing permissions and limitations # under the License. -#utils, dfi and etcdlib are standalone -#(e.g. no dependency on celix framework -add_subdirectory(utils) -add_subdirectory(dfi) -add_subdirectory(etcdlib) -add_subdirectory(promises) -add_subdirectory(pushstreams) +set(RCM_SOURCES src/celix_resource.c src/celix_capability.c src/celix_requirement.c) +set(RCM_PUBLIC_LIBS Celix::utils) +SET(RCM_PRIVATE_LIBS ) -add_subdirectory(framework) +add_library(rcm STATIC ${RCM_SOURCES}) +set_target_properties(rcm PROPERTIES + OUTPUT_NAME "celix_rcm" + VERSION 0.0.1 + SOVERSION 0) +target_link_libraries(rcm PUBLIC ${RCM_PUBLIC_LIBS} PRIVATE ${RCM_PRIVATE_LIBS}) +target_include_directories(rcm PRIVATE src PUBLIC include) +celix_target_hide_symbols(rcm) +add_library(Celix::rcm ALIAS rcm) -#launcher -add_subdirectory(launcher) +if (ENABLE_TESTING) + add_library(rcm_cut STATIC ${RCM_SOURCES}) + target_link_libraries(rcm_cut PUBLIC ${RCM_PUBLIC_LIBS} PRIVATE ${RCM_PRIVATE_LIBS}) + target_include_directories(rcm_cut PRIVATE src PUBLIC include) -#add_subdirectory(event_admin)# event_admin is unstable -add_subdirectory(dependency_manager) -if (CELIX_CXX14) - add_subdirectory(dependency_manager_cxx) + add_subdirectory(gtest) endif () - -# Error Injectors -if (ENABLE_TESTING AND LINKER_WRAP_SUPPORTED) - add_subdirectory(error_injector) -endif () - diff --git a/libs/rcm/README.md b/libs/rcm/README.md new file mode 100644 index 00000000..83c8b9c5 --- /dev/null +++ b/libs/rcm/README.md @@ -0,0 +1,51 @@ +--- +title: RCM library +--- + +<!-- +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. +--> + +## Requirement-Capability-Model (RCM) library +C library based on the OSGi Requirement-Capability-Model, part of +[Chapter 3.3](https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#framework.module.dependencies) +, [Chapter 6](https://docs.osgi.org/specification/osgi.core/8.0.0/framework.resource.html) +and [Chapter 7](https://docs.osgi.org/specification/osgi.core/8.0.0/framework.wiring.html) +in the OSGi Core Specification 8. + +Warning: This library is still under development and not yet ready for production use. + +## TODOs + + - Wiring + - Resolver + +## Base Requirement-Capability-Model + +The Requirement-Capability-Model (RCM) is a model for describing the capabilities and requirements for a resource. +This model can be used in bundle wiring to resolve the dependencies between bundles. + +The following diagram shows the RCM model: + + + +## Requirement-Capability-Model and (Bundle) Wiring + +TODO + +## Requirement-Capability-Model Resolver + +TODO diff --git a/libs/rcm/diagrams/logical-req-cap-model.png b/libs/rcm/diagrams/logical-req-cap-model.png new file mode 100644 index 00000000..d7a14d69 Binary files /dev/null and b/libs/rcm/diagrams/logical-req-cap-model.png differ diff --git a/libs/rcm/diagrams/logical-req-cap-model.puml b/libs/rcm/diagrams/logical-req-cap-model.puml new file mode 100644 index 00000000..e65d3169 --- /dev/null +++ b/libs/rcm/diagrams/logical-req-cap-model.puml @@ -0,0 +1,23 @@ +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. + +@startuml +rectangle "Resource" +rectangle "Capability" +rectangle "Requirement" + +Resource *-up-> "0..n" Requirement +Resource *-down-> "0..n" Capability +@enduml diff --git a/libs/rcm/gtest/CMakeLists.txt b/libs/rcm/gtest/CMakeLists.txt new file mode 100644 index 00000000..943747c8 --- /dev/null +++ b/libs/rcm/gtest/CMakeLists.txt @@ -0,0 +1,38 @@ +# 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. + +set(CMAKE_CXX_STANDARD 17) + +add_executable(test_rcm + src/RequirementCapabilityModelTestSuite.cc +) + +target_link_libraries(test_rcm PRIVATE Celix::rcm GTest::gtest GTest::gtest_main) +add_test(NAME test_rcm COMMAND test_rcm) +setup_target_for_coverage(test_rcm SCAN_DIR ..) + +if (LINKER_WRAP_SUPPORTED) + add_executable(test_rcm_with_error_injection + src/RequirementCapabilityModelWithErrorInjectionTestSuite.cc + ) + target_link_libraries(test_rcm_with_error_injection PRIVATE rcm_cut GTest::gtest GTest::gtest_main) + target_link_libraries(test_rcm_with_error_injection PRIVATE Celix::malloc_ei Celix::utils_ei Celix::array_list_ei Celix::asprintf_ei) + target_include_directories(test_rcm_with_error_injection PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../src) #private headers + add_test(NAME test_rcm_with_error_injection COMMAND test_rcm_with_error_injection) + setup_target_for_coverage(test_rcm_with_error_injection SCAN_DIR ..) +endif () + diff --git a/libs/rcm/gtest/src/RequirementCapabilityModelTestSuite.cc b/libs/rcm/gtest/src/RequirementCapabilityModelTestSuite.cc new file mode 100644 index 00000000..ddf2943b --- /dev/null +++ b/libs/rcm/gtest/src/RequirementCapabilityModelTestSuite.cc @@ -0,0 +1,263 @@ +/* + * 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 <gtest/gtest.h> + +#include "celix/Properties.h" +#include "celix_capability.h" +#include "celix_requirement.h" +#include "celix_resource.h" +#include "celix_err.h" + +class RequirementCapabilityModelTestSuite : public ::testing::Test {}; + +TEST_F(RequirementCapabilityModelTestSuite, TestRequirement) { + celix_requirement_t* req = celix_requirement_create(nullptr, "test-namespace", "(&(capability.attribute1=foo)(capability.attribute2=bar))"); + ASSERT_TRUE(req != nullptr); + EXPECT_STREQ("test-namespace", celix_requirement_getNamespace(req)); + EXPECT_STREQ("(&(capability.attribute1=foo)(capability.attribute2=bar))", celix_requirement_getFilter(req)); + + //test attributes/directives + EXPECT_EQ(0, celix_properties_size(celix_requirement_getAttributes(req))); + EXPECT_EQ(1, celix_properties_size(celix_requirement_getDirectives(req))); //1 filter directive + celix_requirement_addAttribute(req, "test-attribute", "test-attribute-value"); + celix_requirement_addDirective(req, "test-directive", "test-directive-value"); + EXPECT_EQ(1, celix_properties_size(celix_requirement_getAttributes(req))); + EXPECT_EQ(2, celix_properties_size(celix_requirement_getDirectives(req))); + + celix::Properties attr {{"test-attribute1", "test-attribute-value1"}, {"test-attribute2", "test-attribute-value2"}}; + celix_requirement_addAttributes(req, attr.getCProperties()); + celix::Properties dir {{"test-directive1", "test-directive-value1"}, {"test-directive2", "test-directive-value2"}}; + celix_requirement_addDirectives(req, dir.getCProperties()); + EXPECT_EQ(3, celix_properties_size(celix_requirement_getAttributes(req))); + EXPECT_EQ(4, celix_properties_size(celix_requirement_getDirectives(req))); + + //overwrite filter directive + celix_requirement_addDirective(req, "filter", "(&(capability.attribute1=foo)(capability.attribute2=bar3))"); + EXPECT_EQ(4, celix_properties_size(celix_requirement_getDirectives(req))); + EXPECT_STREQ("(&(capability.attribute1=foo)(capability.attribute2=bar3))", celix_requirement_getFilter(req)); + + EXPECT_STREQ(celix_requirement_getDirective(req, "test-directive"), "test-directive-value"); + EXPECT_EQ(nullptr, celix_requirement_getDirective(req, "test-directive-non-existing")); + EXPECT_STREQ(celix_requirement_getAttribute(req, "test-attribute"), "test-attribute-value"); + EXPECT_EQ(nullptr, celix_requirement_getAttribute(req, "test-attribute-non-existing")); + + celix_requirement_t* req2 = celix_requirement_create(nullptr, "test-namespace", nullptr); + ASSERT_TRUE(req2 != nullptr); + EXPECT_EQ(0, celix_properties_size(celix_requirement_getDirectives(req2))); //0 filter directive, because no filter is set on creation + + + celix_requirement_destroy(req); + celix_requirement_destroy(req2); +} + +TEST_F(RequirementCapabilityModelTestSuite, TestRequirementEqualsAndHashCode) { + celix_requirement_t* req = celix_requirement_create(nullptr, "test-namespace", "(&(capability.attribute1=foo)(capability.attribute2=bar))"); + ASSERT_TRUE(req != nullptr); + celix_requirement_addAttribute(req, "test-attribute1", "test-attribute-value1"); + + EXPECT_TRUE(celix_requirement_equals(req, req)); + EXPECT_EQ(celix_requirement_hashCode(req), celix_requirement_hashCode(req)); + + celix_requirement_t* req2 = celix_requirement_create(nullptr, "test-namespace", "(&(capability.attribute1=foo)(capability.attribute2=bar))"); + ASSERT_TRUE(req2 != nullptr); + EXPECT_FALSE(celix_requirement_equals(req, req2)); + EXPECT_NE(celix_requirement_hashCode(req), celix_requirement_hashCode(req2)); + + celix_requirement_addAttribute(req2, "test-attribute1", "test-attribute-value1"); + EXPECT_TRUE(celix_requirement_equals(req, req2)); + EXPECT_EQ(celix_requirement_hashCode(req), celix_requirement_hashCode(req2)); + + celix_requirement_addAttribute(req2, "test-attribute1", "test-attribute-value1-changed"); + EXPECT_FALSE(celix_requirement_equals(req, req2)); + EXPECT_NE(celix_requirement_hashCode(req), celix_requirement_hashCode(req2)); + + celix_requirement_addAttribute(req2, "test-attribute1", "test-attribute-value1"); //overwrite changed + celix_requirement_addDirective(req2, "filter", "(&(capability.attribute1=foo)(capability.attribute2=bar-changed))"); + EXPECT_FALSE(celix_requirement_equals(req, req2)); + EXPECT_NE(celix_requirement_hashCode(req), celix_requirement_hashCode(req2)); + + celix_requirement_t* req3 = celix_requirement_create(nullptr, "test-namespace2", nullptr); + ASSERT_TRUE(req3 != nullptr); + celix_requirement_t* req4 = celix_requirement_create(nullptr, "test-namespace3", nullptr); + ASSERT_TRUE(req4 != nullptr); + EXPECT_FALSE(celix_requirement_equals(req3, req4)); //different namespace + EXPECT_NE(celix_requirement_hashCode(req3), celix_requirement_hashCode(req4)); + + celix_requirement_destroy(req); + celix_requirement_destroy(req2); + celix_requirement_destroy(req3); + celix_requirement_destroy(req4); +} + + +TEST_F(RequirementCapabilityModelTestSuite, TestCapability) { + celix_capability_t* cap = celix_capability_create(nullptr, "test-namespace"); + ASSERT_TRUE(cap != nullptr); + EXPECT_STREQ("test-namespace", celix_capability_getNamespace(cap)); + + //test attributes/directives + EXPECT_EQ(0, celix_properties_size(celix_capability_getAttributes(cap))); + EXPECT_EQ(0, celix_properties_size(celix_capability_getDirectives(cap))); + celix_capability_addAttribute(cap, "test-attribute", "test-attribute-value"); + celix_capability_addDirective(cap, "test-directive", "test-directive-value"); + EXPECT_EQ(1, celix_properties_size(celix_capability_getAttributes(cap))); + EXPECT_EQ(1, celix_properties_size(celix_capability_getDirectives(cap))); + + celix::Properties attr {{"test-attribute1", "test-attribute-value1"}, {"test-attribute2", "test-attribute-value2"}}; + celix_capability_addAttributes(cap, attr.getCProperties()); + celix::Properties dir {{"test-directive1", "test-directive-value1"}, {"test-directive2", "test-directive-value2"}}; + celix_capability_addDirectives(cap, attr.getCProperties()); + EXPECT_EQ(3, celix_properties_size(celix_capability_getAttributes(cap))); + EXPECT_EQ(3, celix_properties_size(celix_capability_getDirectives(cap))); + + EXPECT_STREQ(celix_capability_getDirective(cap, "test-directive"), "test-directive-value"); + EXPECT_EQ(nullptr, celix_capability_getDirective(cap, "test-directive-non-existing")); + EXPECT_STREQ(celix_capability_getAttribute(cap, "test-attribute"), "test-attribute-value"); + EXPECT_EQ(nullptr, celix_capability_getAttribute(cap, "test-attribute-non-existing")); + + celix_capability_destroy(cap); +} + +TEST_F(RequirementCapabilityModelTestSuite, TestCapabilityEqualsAndHashCode) { + celix_capability_t* cap = celix_capability_create(nullptr, "test-namespace"); + ASSERT_TRUE(cap != nullptr); + celix_capability_addDirective(cap, "test-directive", "test-directive-value"); + celix_capability_addAttribute(cap, "test-attribute", "test-attribute-value"); + + EXPECT_TRUE(celix_capability_equals(cap, cap)); + EXPECT_EQ(celix_capability_hashCode(cap), celix_capability_hashCode(cap)); + + celix_capability_t* cap2 = celix_capability_create(nullptr, "test-namespace"); + ASSERT_TRUE(cap2 != nullptr); + EXPECT_FALSE(celix_capability_equals(cap, cap2)); + EXPECT_NE(celix_capability_hashCode(cap), celix_capability_hashCode(cap2)); + + celix_capability_addDirective(cap2, "test-directive", "test-directive-value"); + celix_capability_addAttribute(cap2, "test-attribute", "test-attribute-value"); + EXPECT_TRUE(celix_capability_equals(cap, cap2)); + EXPECT_EQ(celix_capability_hashCode(cap), celix_capability_hashCode(cap2)); + + celix_capability_addAttribute(cap2, "test-attribute", "test-attribute-value-changed"); + EXPECT_FALSE(celix_capability_equals(cap, cap2)); + EXPECT_NE(celix_capability_hashCode(cap), celix_capability_hashCode(cap2)); + + celix_capability_addAttribute(cap2, "test-attribute", "test-attribute-value"); //overwrite changed + celix_capability_addDirective(cap2, "test-directive", "test-directive-value-changed"); + EXPECT_FALSE(celix_capability_equals(cap, cap2)); + EXPECT_NE(celix_capability_hashCode(cap), celix_capability_hashCode(cap2)); + + celix_capability_t* cap3 = celix_capability_create(nullptr, "test-namespace2"); + ASSERT_TRUE(cap3 != nullptr); + celix_capability_t* cap4 = celix_capability_create(nullptr, "test-namespace3"); + ASSERT_TRUE(cap4 != nullptr); + EXPECT_FALSE(celix_capability_equals(cap3, cap4)); //different namespace + EXPECT_NE(celix_capability_hashCode(cap3), celix_capability_hashCode(cap4)); + + celix_capability_destroy(cap); + celix_capability_destroy(cap2); + celix_capability_destroy(cap3); + celix_capability_destroy(cap4); +} + +TEST_F(RequirementCapabilityModelTestSuite, TestNoNamespaceForCapabilityAndRequirement) { + celix_err_resetErrors(); + + celix_capability_t* cap = celix_capability_create(nullptr, nullptr); + EXPECT_TRUE(cap == nullptr); + const char* err = celix_err_popLastError(); + EXPECT_TRUE(err != nullptr); + EXPECT_TRUE(strcasestr(err, "namespace") != nullptr) << "Error message should contain 'namespace' but was: " << err; + + celix_requirement_t* req = celix_requirement_create(nullptr, nullptr, nullptr); + EXPECT_TRUE(req == nullptr); + err = celix_err_popLastError(); + EXPECT_TRUE(err != nullptr); + EXPECT_TRUE(strcasestr(err, "namespace") != nullptr) << "Error message should contain 'namespace' but was: " << err; +} + +TEST_F(RequirementCapabilityModelTestSuite, TestResource) { + celix_resource_t* res = celix_resource_create(); + ASSERT_TRUE(res != nullptr); + EXPECT_EQ(0, celix_arrayList_size(celix_resource_getRequirements(res, nullptr))); + EXPECT_EQ(0, celix_arrayList_size(celix_resource_getCapabilities(res, nullptr))); + + celix_resource_t* res2 = celix_resource_create(); + ASSERT_TRUE(res2 != nullptr); + EXPECT_EQ(0, celix_arrayList_size(celix_resource_getRequirements(res, nullptr))); + EXPECT_EQ(0, celix_arrayList_size(celix_resource_getCapabilities(res, nullptr))); + + celix_requirement_t* req = celix_requirement_create(res, "test-namespace", "(&(capability.attribute1=foo)(capability.attribute2=bar))"); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addRequirement(res, req)); + EXPECT_EQ(res, celix_requirement_getResource(req)); + EXPECT_EQ(1, celix_arrayList_size(celix_resource_getRequirements(res, nullptr))); + req = celix_requirement_create(res, "test-namespace2", nullptr); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addRequirement(res, req)); + EXPECT_EQ(2, celix_arrayList_size(celix_resource_getRequirements(res, nullptr))); + + celix_capability_t *cap = celix_capability_create(res, "test-namespace"); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addCapability(res, cap)); + EXPECT_EQ(res, celix_capability_getResource(cap)); + EXPECT_EQ(1, celix_arrayList_size(celix_resource_getCapabilities(res, nullptr))); + + req = celix_requirement_create(res2, "test-namespace2", nullptr); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addRequirement(res2, req)); + req = celix_requirement_create(res2, "test-namespace", "(&(capability.attribute1=foo)(capability.attribute2=bar))"); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addRequirement(res2, req)); + cap = celix_capability_create(res2, "test-namespace"); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addCapability(res2, cap)); + + //test get capabilities/requirements by namespace + req = celix_requirement_create(res, "test-namespace2", nullptr); + cap = celix_capability_create(res, "test-namespace2"); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addRequirement(res, req)); + EXPECT_EQ(CELIX_SUCCESS, celix_resource_addCapability(res, cap)); + EXPECT_EQ(3, celix_arrayList_size(celix_resource_getRequirements(res, nullptr))); + EXPECT_EQ(2, celix_arrayList_size(celix_resource_getCapabilities(res, nullptr))); + const celix_array_list_t* caps = celix_resource_getCapabilities(res, "test-namespace"); + EXPECT_EQ(1, celix_arrayList_size(caps)); + const celix_array_list_t* reqs = celix_resource_getRequirements(res, "test-namespace"); + EXPECT_EQ(1, celix_arrayList_size(reqs)); + + celix_resource_destroy(res); + celix_resource_destroy(res2); +} + +TEST_F(RequirementCapabilityModelTestSuite, TestCapabilityAndRequirementWithWrongResource) { + celix_resource_t* res = celix_resource_create(); + ASSERT_TRUE(res != nullptr); + + celix_capability_t* cap = celix_capability_create(nullptr, "test-namespace"); + celix_requirement_t* req = celix_requirement_create(nullptr, "test-namespace", nullptr); + + celix_err_resetErrors(); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_resource_addCapability(res, cap)); + EXPECT_EQ(1, celix_err_getErrorCount()); + const char* err = celix_err_popLastError(); + EXPECT_TRUE(strcasestr(err, "capability") != nullptr) << "Error message should contain 'capability' but was: " << err; + celix_capability_destroy(cap); + + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_resource_addRequirement(res, req)); + EXPECT_EQ(1, celix_err_getErrorCount()); + err = celix_err_popLastError(); + EXPECT_TRUE(strcasestr(err, "requirement") != nullptr) << "Error message should contain 'requirement' but was: " << err; + celix_requirement_destroy(req); + + celix_resource_destroy(res); +} diff --git a/libs/rcm/gtest/src/RequirementCapabilityModelWithErrorInjectionTestSuite.cc b/libs/rcm/gtest/src/RequirementCapabilityModelWithErrorInjectionTestSuite.cc new file mode 100644 index 00000000..fe6cb33f --- /dev/null +++ b/libs/rcm/gtest/src/RequirementCapabilityModelWithErrorInjectionTestSuite.cc @@ -0,0 +1,133 @@ +/* + * 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 <gtest/gtest.h> + +#include <memory> + +#include "malloc_ei.h" +#include "celix_utils_ei.h" +#include "celix_array_list_ei.h" +#include "asprintf_ei.h" + +#include "celix_capability.h" +#include "celix_requirement.h" +#include "celix_resource.h" +#include "celix_err.h" + +class RequirementCapabilityModelWithErrorInjectionTestSuite : public ::testing::Test { +public: + RequirementCapabilityModelWithErrorInjectionTestSuite() = default; + + ~RequirementCapabilityModelWithErrorInjectionTestSuite() override { + celix_ei_expect_malloc(nullptr, 0, nullptr); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_create(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_add(nullptr, 0, CELIX_SUCCESS); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_vasprintf(nullptr, 0, 0); + celix_err_resetErrors(); + } +}; + +TEST_F(RequirementCapabilityModelWithErrorInjectionTestSuite, TestRequirementErrorHandling) { + //inject error on first malloc call from celix_requirement_create + celix_ei_expect_malloc((void*)celix_requirement_create, 0, nullptr); + celix_requirement_t* req = celix_requirement_create(nullptr, "test", nullptr); + EXPECT_EQ(nullptr, req); + EXPECT_EQ(1, celix_err_getErrorCount()); + + + //inject error on first celix_utils_strdup call from celix_requirement_create + celix_ei_expect_celix_utils_strdup((void*)celix_requirement_create, 0, nullptr); + req = celix_requirement_create(nullptr, "test", nullptr); + EXPECT_EQ(nullptr, req); + EXPECT_EQ(2, celix_err_getErrorCount()); +} + +TEST_F(RequirementCapabilityModelWithErrorInjectionTestSuite, TestCapabilityErrorHandling) { + //inject error on first malloc call from celix_capability_create + celix_ei_expect_malloc((void*)celix_capability_create, 0, nullptr); + celix_capability_t* cap = celix_capability_create(nullptr, "test"); + EXPECT_EQ(nullptr, cap); + EXPECT_EQ(1, celix_err_getErrorCount()); + + + //inject error on first celix_utils_strdup call from celix_capability_create + celix_ei_expect_celix_utils_strdup((void*)celix_capability_create, 0, nullptr); + cap = celix_capability_create(nullptr, "test"); + EXPECT_EQ(nullptr, cap); + EXPECT_EQ(2, celix_err_getErrorCount()); +} + +TEST_F(RequirementCapabilityModelWithErrorInjectionTestSuite, TestResourceErrorHandling) { + //inject error on first malloc call from celix_resource_create + celix_ei_expect_malloc((void*)celix_resource_create, 0, nullptr); + celix_resource_t* res = celix_resource_create(); + EXPECT_EQ(nullptr, res); + EXPECT_EQ(1, celix_err_getErrorCount()); + + //inject error on first celix_arrayList_createWithOptions call from celix_resource_create + celix_ei_expect_celix_arrayList_createWithOptions((void*)celix_resource_create, 0, nullptr); + res = celix_resource_create(); + EXPECT_EQ(nullptr, res); + EXPECT_EQ(2, celix_err_getErrorCount()); + + res = celix_resource_create(); + EXPECT_NE(nullptr, res); + celix_capability_t* cap = celix_capability_create(res, "test"); + EXPECT_NE(nullptr, cap); + celix_requirement_t* req = celix_requirement_create(res, "test", nullptr); + EXPECT_NE(nullptr, req); + + //inject error on first celix_arrayList_create call from celix_resource_addCapability + celix_ei_expect_celix_arrayList_create((void*)celix_resource_addCapability, 0, nullptr); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addCapability(res, cap)); + EXPECT_EQ(3, celix_err_getErrorCount()); + + //inject error on first celix_arrayList_add call from celix_resource_addCapability + celix_ei_expect_celix_arrayList_add((void*)celix_resource_addCapability, 0, CELIX_ENOMEM); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addCapability(res, cap)); + EXPECT_EQ(4, celix_err_getErrorCount()); + + //inject error on second celix_arrayList_add call from celix_resource_addCapability + celix_ei_expect_celix_arrayList_add((void*)celix_resource_addCapability, 0, CELIX_ENOMEM, 2); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addCapability(res, cap)); + EXPECT_EQ(5, celix_err_getErrorCount()); + + //inject error on first celix_arrayList_create call from celix_resource_addRequirement + celix_ei_expect_celix_arrayList_create((void*)celix_resource_addRequirement, 0, nullptr); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addRequirement(res, req)); + EXPECT_EQ(6, celix_err_getErrorCount()); + + //inject error on first celix_arrayList_add call from celix_resource_addRequirement + celix_ei_expect_celix_arrayList_add((void*)celix_resource_addRequirement, 0, CELIX_ENOMEM); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addRequirement(res, req)); + EXPECT_EQ(7, celix_err_getErrorCount()); + + //inject error on second celix_arrayList_add call from celix_resource_addRequirement + celix_ei_expect_celix_arrayList_add((void*)celix_resource_addRequirement, 0, CELIX_ENOMEM, 2); + EXPECT_EQ(CELIX_ENOMEM, celix_resource_addRequirement(res, req)); + EXPECT_EQ(8, celix_err_getErrorCount()); + + celix_capability_destroy(cap); + celix_requirement_destroy(req); + celix_resource_destroy(res); +} diff --git a/libs/rcm/include/celix_capability.h b/libs/rcm/include/celix_capability.h new file mode 100644 index 00000000..09229115 --- /dev/null +++ b/libs/rcm/include/celix_capability.h @@ -0,0 +1,161 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_CAPABILITY_H +#define CELIX_CELIX_CAPABILITY_H + +#include <stdbool.h> + +#include "celix_rcm_types.h" +#include "celix_properties.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @file celix_capability.h +* @brief The celix_capability_t is a struct which represents a capability. +* +* @thread_safety none +*/ + +/** + * @brief Creates a new capability. + * + * In case of a error, an error message is added to celix_err. + * + * @param[in] resource The resource which contains the capability. Can be NULL. + * @param[in] ns The namespace of the capability. + * @return The new capability. + * @retval NULL If an error occurred. + */ +celix_capability_t* celix_capability_create( + const celix_resource_t* resource, + const char* ns); + +/** + * @brief Destroys the capability. + * @param[in] capability The capability to destroy. Can be NULL. + */ +void celix_capability_destroy(celix_capability_t* capability); + +/** + * @brief Check if 2 capabilities are equal. + */ +bool celix_capability_equals(const celix_capability_t* cap1, const celix_capability_t* cap2); + +/** + * @brief Returns the hash code of the capability. + */ +unsigned int celix_capability_hashCode(const celix_capability_t* cap); + +/** + * @brief Returns the resource which contains the capability. + * @param[in] cap The capability. + * @return The resource or NULL if no resource is set. + */ +const celix_resource_t* celix_capability_getResource(const celix_capability_t* cap); + +/** + * @brief Returns the namespace of the capability. + * @param[in] cap The capability. + * @return The namespace of the capability. + */ +const char* celix_capability_getNamespace(const celix_capability_t* cap); + +/** + * @brief Returns the attributes of the capability. + * @param[in] cap The capability. + * @return The attributes of the capability. Will be an empty properties if no attributes are set. + */ +const celix_properties_t* celix_capability_getAttributes(const celix_capability_t* cap); + +/** + * @brief Returns the directives of the capability. + * @param[in] cap The capability. + * @return The directives of the capability. Will be an empty properties if no directives are set. + */ +const celix_properties_t* celix_capability_getDirectives(const celix_capability_t* cap); + +/** + * @brief Returns the value of the attribute with the given key. + * @param[in] cap The capability. + * @param[in] key The key of the attribute. + * @return The value of the attribute or NULL if the attribute is not set. + */ +const char* celix_capability_getAttribute(const celix_capability_t* cap, const char* key); + +/** + * @brief Returns the value of the directive with the given key. + * @param[in] cap The capability. + * @param[in] key The key of the directive. + * @return The value of the directive or NULL if the directive is not set. + */ +const char* celix_capability_getDirective(const celix_capability_t* cap, const char* key); + +/** + * @brief Add a new attribute to the capability. + * + * Note replaces an existing attribute is the key is already set. + * + * @param[in] cap The capability. + * @param[in] key The key of the attribute. + * @param[in] value The value of the attribute. + */ +void celix_capability_addAttribute(celix_capability_t* cap, const char* key, const char* value); + +/** + * @brief Add a new attributes to the capability. + * + * Note replaces existing attributes is some of the provided keys are already set. + * + * @param[in] cap The capability. + * @param[in] attributes The attributes to add. + * Note the attributes are copied, so the caller is still owner of the properties. + */ +void celix_capability_addAttributes(celix_capability_t* cap, const celix_properties_t* attributes); + +/** + * @brief Add a new directive to the capability. + * + * Note replaces an existing directive is the key is already set. + * + * @param[in] cap The capability. + * @param[in] key The key of the directive. + * @param[in] value The value of the directive. + */ +void celix_capability_addDirective(celix_capability_t* cap, const char* key, const char* value); + +/** + * @brief Add a new directives to the capability. + * + * Note replaces existing directives is some of the provided keys are already set. + * + * @param[in] cap The capability. + * @param[in] directives The directives to add. + * Note the directives are copied, so the caller is still owner of the properties. + */ +void celix_capability_addDirectives(celix_capability_t* cap, const celix_properties_t* directives); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_CELIX_CAPABILITY_H diff --git a/libs/rcm/include/celix_rcm_types.h b/libs/rcm/include/celix_rcm_types.h new file mode 100644 index 00000000..278e1c10 --- /dev/null +++ b/libs/rcm/include/celix_rcm_types.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_RCM_TYPES_H +#define CELIX_CELIX_RCM_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @file celix_rcm_types.h +* @brief The celix_rcm_types.h header file contains the forward declarations of the celix_resource_t, celix_capability_t and celix_requirement_t types. +*/ + +//forward declarations of opaque types +typedef struct celix_resource celix_resource_t; +typedef struct celix_capability celix_capability_t; +typedef struct celix_requirement celix_requirement_t; + + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_CELIX_RCM_TYPES_H diff --git a/libs/rcm/include/celix_requirement.h b/libs/rcm/include/celix_requirement.h new file mode 100644 index 00000000..a94a14af --- /dev/null +++ b/libs/rcm/include/celix_requirement.h @@ -0,0 +1,173 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_REQUIREMENT_H +#define CELIX_CELIX_REQUIREMENT_H + +#include "celix_rcm_types.h" +#include "celix_properties.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @file celix_requirement.h +* @brief The celix_requirement_t is a requirement for a capability. +* +* A requirement is a requirement for a capability. A requirement has a namespace, a filter and a set of directives and attributes. +* The namespace is used to identify the type of requirement. The filter is used to filter the capabilities. +* The directives and attributes are used to configure the requirement. +* +* @thread_safety none +*/ + +/** + * @brief Creates a new requirement. + * + * In case of a error, an error message is added to celix_err. + * + * @param[in] resource The resource which contains the requirement. Can be NULL. + * @param[in] ns The namespace of the requirement. + * @param[in] filter The filter of the requirement. Can be NULL. + * @return The new requirement. + * @retval NULL If an error occurred. + */ +celix_requirement_t* celix_requirement_create( + celix_resource_t* resource, + const char* ns, + const char* filter); + + +/** + * @brief Destroys the requirement. + * @param[in] requirement The requirement to destroy. Can be NULL. + */ +void celix_requirement_destroy(celix_requirement_t* requirement); + +/** + * @brief Check if 2 requirements are equal. + */ +bool celix_requirement_equals(const celix_requirement_t* req1, const celix_requirement_t* req2); + +/** + * @brief Returns the hash code of the requirement. + */ +unsigned int celix_requirement_hashCode(const celix_requirement_t* req); + +/** + * @brief Returns the resource which contains the requirement. + * @param[in] req The requirement. + * @return The resource which contains the requirement or NULL if the requirement is not part of a resource. + */ +const celix_resource_t* celix_requirement_getResource(const celix_requirement_t* req); + +/** + * @brief Returns the namespace of the requirement. + * @param[in] req The requirement. + * @return The namespace of the requirement. + */ +const char* celix_requirement_getNamespace(const celix_requirement_t* req); + +/** + * @brief Returns the filter of the requirement. + * @param[in] req The requirement. + * @return The filter of the requirement. + */ +const char* celix_requirement_getFilter(const celix_requirement_t* req); + +/** + * @brief Returns the directives of the requirement. + * @param[in] req The requirement. + * @return The directives of the requirement. Will be an empty properties if no directives are set. + */ +const celix_properties_t* celix_requirement_getDirectives(const celix_requirement_t* req); + +/** + * @brief Returns the value of the directive with the given key. + * @param[in] req The requirement. + * @param[in] key The key of the directive. + * @return The value of the directive or NULL if the directive is not set. + */ +const char* celix_requirement_getDirective(const celix_requirement_t* req, const char* key); + +/** + * @brief Returns the attributes of the requirement. + * @param[in] req The requirement. + * @return The attributes of the requirement. Will be an empty properties if no attributes are set. + */ +const celix_properties_t* celix_requirement_getAttributes(const celix_requirement_t* req); + +/** + * @brief Returns the value of the attribute with the given key. + * @param[in] req The requirement. + * @param[in] key The key of the attribute. + * @return The value of the attribute or NULL if the attribute is not set. + */ +const char* celix_requirement_getAttribute(const celix_requirement_t* req, const char* key); + +/** + * @brief Add a new directive to the requirement. + * + * Note replaces an existing directive is the key is already set. + * + * @param[in] req The requirement. + * @param[in] key The key of the directive. + * @param[in] value The value of the directive. + */ +void celix_requirement_addDirective(celix_requirement_t* req, const char* key, const char* value); + +/** + * @brief Add a new directives to the requirement. + * + * Note replaces existing directives is some of the provided keys are already set. + * + * @param[in] req The requirement. + * @param[in] directives The directives to add. + * Note the directives are copied, so the caller is still owner of the properties. + */ +void celix_requirement_addDirectives(celix_requirement_t* req, const celix_properties_t* directives); + +/** + * @brief Add a new attribute to the requirement. + * + * Note replaces an existing attribute is the key is already set. + * + * @param[in] req The requirement. + * @param[in] key The key of the attribute. + * @param[in] value The value of the attribute. + */ +void celix_requirement_addAttribute(celix_requirement_t* req, const char* key, const char* value); + +/** + * @brief Add a new attributes to the requirement. + * + * Note replaces existing attributes is some of the provided keys are already set. + * + * @param[in] req The requirement. + * @param[in] attributes The attributes to add. + * Note the attributes are copied, so the caller is still owner of the properties. + */ +void celix_requirement_addAttributes(celix_requirement_t* req, const celix_properties_t* attributes); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_CELIX_REQUIREMENT_H diff --git a/libs/rcm/include/celix_resource.h b/libs/rcm/include/celix_resource.h new file mode 100644 index 00000000..11e7c98c --- /dev/null +++ b/libs/rcm/include/celix_resource.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_RESOURCE_H +#define CELIX_CELIX_RESOURCE_H + +#include "celix_rcm_types.h" +#include "celix_array_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @file celix_resource.h +* @brief The celix_resource_t is a resource in the RCM. +* +* A resource is a collection of capabilities and requirements. +* +* @thread_safety none +*/ + +/** + * @brief Creates a new resource. + * + * In case of a error, an error message is added to celix_err. + * + * @return A new resource. + * @retval NULL If the resource could not be created. + */ +celix_resource_t* celix_resource_create(); + +/** + * @brief Destroys the resource. + * @param[in] resource The resource to destroy. Can be NULL. + */ +void celix_resource_destroy(celix_resource_t* resource); + +/** + * @brief Returns the capabilities of the resource. + * + * Will return all resource capabilities if the provided namespace is NULL. + * + * @param[in] res The resource. + * @param[in] ns The namespace of the capabilities. Can be NULL. + * @return The capabilities of the resource. Will be an empty list if the resource has no capabilities or + * has no capabilities with the provided namespace. + */ +const celix_array_list_t* celix_resource_getCapabilities(const celix_resource_t* res, const char* ns); + +/** + * @brief Returns the requirements of the resource for the provided namespace. + * + * Will return all resource requirements if the provided namespace is NULL. + * + * @param[in] res The resource. + * @param[in] ns The namespace of the requirements. Can be NULL. + * @return The requirements of the resource. Will be an empty list if the resource has no requirements or + * has no requirements with the provided namespace. + */ +const celix_array_list_t* celix_resource_getRequirements(const celix_resource_t* res, const char* ns); + +/** + * @brief Adds a capability to the resource. + * + * The capability resource must be the same as this resource or a CELIX_ILLEGAL_ARGUMENT error is returned. + * + * In case of a error, an error message is added to celix_err. + * + * @param[in] res The resource. + * @param[in] cap The capability to add. + * @return CELIX_SUCCESS if the capability was added successfully. + * @retval CELIX_ILLEGAL_ARGUMENT If the capability resource is not the same as this resource. + * @retval ENOMEM If there is not enough memory to add the capability. + */ +celix_status_t celix_resource_addCapability(celix_resource_t* res, celix_capability_t* cap); + +/** + * @brief Adds a requirement to the resource. + * + * In case of a error, an error message is added to celix_err. + * + * @param[in] res The resource. + * @param[in] req The requirement to add. + * @return CELIX_SUCCESS if the requirement was added successfully. + * @retval CELIX_ILLEGAL_ARGUMENT If the requirement resource is not the same as this resource. + * @retval ENOMEM If there is not enough memory to add the requirement. + */ +celix_status_t celix_resource_addRequirement(celix_resource_t* res, celix_requirement_t* req); + + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_CELIX_RESOURCE_H diff --git a/libs/rcm/src/celix_capability.c b/libs/rcm/src/celix_capability.c new file mode 100644 index 00000000..93aeaf5b --- /dev/null +++ b/libs/rcm/src/celix_capability.c @@ -0,0 +1,172 @@ +/* + * 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 <stdlib.h> + +#include "celix_utils.h" +#include "celix_capability.h" +#include "celix_err.h" + +struct celix_capability { + const celix_resource_t* resource; //weak ref + char* ns; + celix_properties_t* attributes; + celix_properties_t* directives; +}; + +celix_capability_t* celix_capability_create( + const celix_resource_t* resource, + const char* ns) { + + if (celix_utils_isStringNullOrEmpty(ns)) { + celix_err_push("Failed to create celix_capability_t. Namespace is NULL or empty."); + return NULL; + } + + celix_capability_t* cap = malloc(sizeof(*cap)); + if (cap == NULL) { + celix_err_push("Failed to allocate celix_capability_t. Out of memory."); + return NULL; + } + + cap->resource = resource; + cap->ns = celix_utils_strdup(ns); + cap->attributes = celix_properties_create(); + cap->directives = celix_properties_create(); + if (cap->ns == NULL || cap->attributes == NULL || cap->directives == NULL) { + celix_err_push("Failed to allocate celix_capability_t fields. Out of memory."); + celix_capability_destroy(cap); + return NULL; + } + + return cap; +} + +void celix_capability_destroy(celix_capability_t* capability) { + if (capability != NULL) { + free(capability->ns); + celix_properties_destroy(capability->attributes); + celix_properties_destroy(capability->directives); + free(capability); + } +} + +bool celix_capability_equals(const celix_capability_t* cap1, const celix_capability_t* cap2) { + if (cap1 == cap2) { + return true; + } + + if (celix_properties_size(cap1->attributes) != celix_properties_size(cap2->attributes) || + celix_properties_size(cap1->directives) != celix_properties_size(cap2->directives)) { + return false; + } + + if (!celix_utils_stringEquals(cap1->ns, cap2->ns)) { + return false; + } + + //compare attributes + bool equals = true; + const char* visit; + CELIX_PROPERTIES_FOR_EACH(cap1->attributes, visit) { + const char* value1 = celix_properties_get(cap1->attributes, visit, NULL); + const char* value2 = celix_properties_get(cap2->attributes, visit, NULL); + if (!celix_utils_stringEquals(value1, value2)) { + equals = false; + break; + } + } + if (!equals) { + return false; + } + CELIX_PROPERTIES_FOR_EACH(cap1->directives, visit) { + const char* value1 = celix_properties_get(cap1->directives, visit, NULL); + const char* value2 = celix_properties_get(cap2->directives, visit, NULL); + if (!celix_utils_stringEquals(value1, value2)) { + equals = false; + break; + } + } + return equals; +} + +unsigned int celix_capability_hashCode(const celix_capability_t* cap) { + unsigned int hash = celix_utils_stringHash(cap->ns); + const char* visit; + + CELIX_PROPERTIES_FOR_EACH(cap->attributes, visit) { + const char* value = celix_properties_get(cap->attributes, visit, NULL); + hash += celix_utils_stringHash(visit); + hash += celix_utils_stringHash(value); + } + CELIX_PROPERTIES_FOR_EACH(cap->directives, visit) { + const char* value = celix_properties_get(cap->directives, visit, NULL); + hash += celix_utils_stringHash(visit); + hash += celix_utils_stringHash(value); + } + return hash; +} + +const celix_resource_t* celix_capability_getResource(const celix_capability_t* cap) { + return cap->resource; +} + +const char* celix_capability_getNamespace(const celix_capability_t* cap) { + return cap->ns; +} + +const celix_properties_t* celix_capability_getAttributes(const celix_capability_t* cap) { + return cap->attributes; +} + +const celix_properties_t* celix_capability_getDirectives(const celix_capability_t* cap) { + return cap->directives; +} + +const char* celix_capability_getAttribute(const celix_capability_t* cap, const char* key) { + return celix_properties_get(cap->attributes, key, NULL); +} + +const char* celix_capability_getDirective(const celix_capability_t* cap, const char* key) { + return celix_properties_get(cap->directives, key, NULL); +} + +void celix_capability_addAttribute(celix_capability_t* cap, const char* key, const char* value) { + celix_properties_set(cap->attributes, key, value); +} + +void celix_capability_addAttributes(celix_capability_t* cap, const celix_properties_t* attributes) { + const char* visit; + CELIX_PROPERTIES_FOR_EACH(attributes, visit) { + const char* value = celix_properties_get(attributes, visit, NULL); + celix_properties_set(cap->attributes, visit, value); + } +} + +void celix_capability_addDirective(celix_capability_t* cap, const char* key, const char* value) { + celix_properties_set(cap->directives, key, value); +} + +void celix_capability_addDirectives(celix_capability_t* cap, const celix_properties_t* directives) { + const char* visit; + CELIX_PROPERTIES_FOR_EACH(directives, visit) { + const char* value = celix_properties_get(directives, visit, NULL); + celix_properties_set(cap->directives, visit, value); + } +} diff --git a/libs/rcm/src/celix_requirement.c b/libs/rcm/src/celix_requirement.c new file mode 100644 index 00000000..48f3dd45 --- /dev/null +++ b/libs/rcm/src/celix_requirement.c @@ -0,0 +1,185 @@ +/* + * 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 <stdlib.h> + +#include "celix_utils.h" +#include "celix_requirement.h" +#include "celix_err.h" + +#define CELIX_REQUIREMENT_DIRECTIVE_FILTER "filter" + +struct celix_requirement { + const celix_resource_t* resource; //weak ref + char* ns; + celix_properties_t* attributes; + celix_properties_t* directives; +}; + +celix_requirement_t* celix_requirement_create( + celix_resource_t* resource, + const char* ns, + const char* filter) { + + if (celix_utils_isStringNullOrEmpty(ns)) { + celix_err_push("Failed to create celix_requirement_t. Namespace is NULL or empty."); + return NULL; + } + + celix_requirement_t* req = malloc(sizeof(*req)); + if (req == NULL) { + celix_err_push("Failed to allocate celix_requirement_t. Out of memory."); + return NULL; + } + + req->resource = resource; + req->ns = celix_utils_strdup(ns); + req->attributes = celix_properties_create(); + req->directives = celix_properties_create(); + if (req->ns == NULL || req->attributes == NULL || req->directives == NULL) { + celix_err_push("Failed to allocate celix_requirement_t fields. Out of memory."); + celix_requirement_destroy(req); + return NULL; + } + + if (filter != NULL) { + celix_properties_set(req->directives, CELIX_REQUIREMENT_DIRECTIVE_FILTER, filter); + } + + return req; +} + +void celix_requirement_destroy(celix_requirement_t* requirement) { + if (requirement != NULL) { + free(requirement->ns); + celix_properties_destroy(requirement->attributes); + celix_properties_destroy(requirement->directives); + free(requirement); + } +} + +bool celix_requirement_equals(const celix_requirement_t* req1, const celix_requirement_t* req2) { + if (req1 == req2) { + return true; + } + + if (celix_properties_size(req1->attributes) != celix_properties_size(req2->attributes) || + celix_properties_size(req1->directives) != celix_properties_size(req2->directives)) { + return false; + } + + if (!celix_utils_stringEquals(req1->ns, req2->ns)) { + return false; + } + + //compare attributes + bool equals = true; + const char* visit; + CELIX_PROPERTIES_FOR_EACH(req1->attributes, visit) { + const char* val1 = celix_properties_get(req1->attributes, visit, NULL); + const char* val2 = celix_properties_get(req2->attributes, visit, NULL); + if (!celix_utils_stringEquals(val1, val2)) { + equals = false; + break; + } + } + if (!equals) { + return false; + } + + //compare directives + CELIX_PROPERTIES_FOR_EACH(req1->directives, visit) { + const char* val1 = celix_properties_get(req1->directives, visit, NULL); + const char* val2 = celix_properties_get(req2->directives, visit, NULL); + if (!celix_utils_stringEquals(val1, val2)) { + equals = false; + break; + } + } + return equals; +} + +unsigned int celix_requirement_hashCode(const celix_requirement_t* req) { + unsigned int hash = celix_utils_stringHash(req->ns); + const char* visit; + + CELIX_PROPERTIES_FOR_EACH(req->attributes, visit) { + const char* val = celix_properties_get(req->attributes, visit, NULL); + hash += celix_utils_stringHash(visit); + hash += celix_utils_stringHash(val); + } + CELIX_PROPERTIES_FOR_EACH(req->directives, visit) { + const char* val = celix_properties_get(req->directives, visit, NULL); + hash += celix_utils_stringHash(visit); + hash += celix_utils_stringHash(val); + } + return hash; +} + +const celix_resource_t* celix_requirement_getResource(const celix_requirement_t* req) { + return req->resource; +} + +const char* celix_requirement_getNamespace(const celix_requirement_t* req) { + return req->ns; +} + +const char* celix_requirement_getFilter(const celix_requirement_t* req) { + return celix_properties_get(req->directives, CELIX_REQUIREMENT_DIRECTIVE_FILTER, NULL); +} + +const celix_properties_t* celix_requirement_getDirectives(const celix_requirement_t* req) { + return req->directives; +} + +const char* celix_requirement_getDirective(const celix_requirement_t* req, const char* key) { + return celix_properties_get(req->directives, key, NULL); +} + +const celix_properties_t* celix_requirement_getAttributes(const celix_requirement_t* req) { + return req->attributes; +} + +const char* celix_requirement_getAttribute(const celix_requirement_t* req, const char* key) { + return celix_properties_get(req->attributes, key, NULL); +} + +void celix_requirement_addDirective(celix_requirement_t* req, const char* key, const char* value) { + celix_properties_set(req->directives, key, value); +} + +void celix_requirement_addAttribute(celix_requirement_t* req, const char* key, const char* value) { + celix_properties_set(req->attributes, key, value); +} + +void celix_requirement_addDirectives(celix_requirement_t* req, const celix_properties_t* directives) { + const char* visit; + CELIX_PROPERTIES_FOR_EACH(directives, visit) { + const char* val = celix_properties_get(directives, visit, NULL); + celix_requirement_addDirective(req, visit, val); + } +} + +void celix_requirement_addAttributes(celix_requirement_t* req, const celix_properties_t* attributes) { + const char* visit; + CELIX_PROPERTIES_FOR_EACH(attributes, visit) { + const char* val = celix_properties_get(attributes, visit, NULL); + celix_requirement_addAttribute(req, visit, val); + } +} diff --git a/libs/rcm/src/celix_resource.c b/libs/rcm/src/celix_resource.c new file mode 100644 index 00000000..a1e53c57 --- /dev/null +++ b/libs/rcm/src/celix_resource.c @@ -0,0 +1,155 @@ +/* + * 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 <stdlib.h> + +#include "celix_string_hash_map.h" +#include "celix_err.h" +#include "celix_resource.h" +#include "celix_capability.h" +#include "celix_requirement.h" + +struct celix_resource { + celix_array_list_t* allCapabilities; + celix_array_list_t* allRequirements; + celix_string_hash_map_t* capabilitiesByNamespace; //note weak ref to capabilities in allCapabilities + celix_string_hash_map_t* requirementsByNamespace; //note weak ref to requirements in allRequirements +}; + +static void celix_resource_destroyCapability(void* capability) { + celix_capability_destroy((celix_capability_t*)capability); +} + +static void celix_resource_destroyRequirement(void* requirement) { + celix_requirement_destroy((celix_requirement_t*)requirement); +} + +static void celix_resource_freeReqOrCapList(void* list) { + celix_arrayList_destroy((celix_array_list_t*)list); +} + +celix_resource_t* celix_resource_create() { + celix_resource_t* res = malloc(sizeof(*res)); + if (res == NULL) { + celix_err_push("Failed to allocate celix_resource_t. Out of memory."); + return NULL; + } + + celix_array_list_create_options_t opts = CELIX_EMPTY_ARRAY_LIST_CREATE_OPTIONS; + opts.simpleRemovedCallback = celix_resource_destroyCapability; + res->allCapabilities = celix_arrayList_createWithOptions(&opts); + + opts.simpleRemovedCallback = celix_resource_destroyRequirement; + res->allRequirements = celix_arrayList_createWithOptions(&opts); + + celix_string_hash_map_create_options_t mapOpts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + mapOpts.simpleRemovedCallback = celix_resource_freeReqOrCapList; + res->capabilitiesByNamespace = celix_stringHashMap_createWithOptions(&mapOpts); + res->requirementsByNamespace = celix_stringHashMap_createWithOptions(&mapOpts); + + if ( res->allCapabilities == NULL || + res->allRequirements == NULL || + res->capabilitiesByNamespace == NULL || + res->requirementsByNamespace == NULL) { + celix_err_push("Failed to allocate celix_resource_t fields. Out of memory."); + celix_resource_destroy(res); + return NULL; + } + return res; +} + +void celix_resource_destroy(celix_resource_t* resource) { + if (resource != NULL) { + celix_arrayList_destroy(resource->allCapabilities); + celix_arrayList_destroy(resource->allRequirements); + celix_stringHashMap_destroy(resource->capabilitiesByNamespace); + celix_stringHashMap_destroy(resource->requirementsByNamespace); + free(resource); + } +} + +const celix_array_list_t* celix_resource_getCapabilities(const celix_resource_t* res, const char* ns) { + if (ns != NULL) { + return celix_stringHashMap_get(res->capabilitiesByNamespace, ns); + } + return res->allCapabilities; +} + +const celix_array_list_t* celix_resource_getRequirements(const celix_resource_t* res, const char* ns) { + if (ns != NULL) { + return celix_stringHashMap_get(res->requirementsByNamespace, ns); + } + return res->allRequirements; +} + +celix_status_t celix_resource_addCapability(celix_resource_t* res, celix_capability_t* cap) { + if (celix_capability_getResource(cap) != res) { + celix_err_pushf( + "Capability is not added to the correct resource. (Capability is added to resource %p, but resource %p is expected.)", + celix_capability_getResource(cap), res); + return CELIX_ILLEGAL_ARGUMENT; + } + + const char* ns = celix_capability_getNamespace(cap); + celix_array_list_t* caps = celix_stringHashMap_get(res->capabilitiesByNamespace, ns); + if (caps == NULL) { + caps = celix_arrayList_create(); + if (caps == NULL) { + goto err_handling; + } + celix_stringHashMap_put(res->capabilitiesByNamespace, ns, caps); + } + CELIX_GOTO_IF_ERR(celix_arrayList_add(caps, cap), err_handling); + CELIX_GOTO_IF_ERR(celix_arrayList_add(res->allCapabilities, cap), err_handling); + return CELIX_SUCCESS; +err_handling: + celix_err_push("Failed to add capability to resource. Out of memory."); + if (caps != NULL) { + celix_arrayList_remove(caps, cap); + } + return ENOMEM; +} + +celix_status_t celix_resource_addRequirement(celix_resource_t* res, celix_requirement_t* req) { + if (celix_requirement_getResource(req) != res) { + celix_err_pushf( + "Requirement is not added to the correct resource. (Requirement is added to resource %p, but resource %p is expected.)", + celix_requirement_getResource(req), res); + return CELIX_ILLEGAL_ARGUMENT; + } + + const char* ns = celix_requirement_getNamespace(req); + celix_array_list_t* reqs = celix_stringHashMap_get(res->requirementsByNamespace, ns); + if (reqs == NULL) { + reqs = celix_arrayList_create(); + if (reqs == NULL) { + goto err_handling; + } + celix_stringHashMap_put(res->requirementsByNamespace, ns, reqs); + } + CELIX_GOTO_IF_ERR(celix_arrayList_add(reqs, req), err_handling); + CELIX_GOTO_IF_ERR(celix_arrayList_add(res->allRequirements, req), err_handling); + return CELIX_SUCCESS; +err_handling: + celix_err_push("Failed to add requirement to resource. Out of memory."); + if (reqs != NULL) { + celix_arrayList_remove(reqs, req); + } + return ENOMEM; +}
