diff --git a/core-framework/include/core/ObjectFactory.h b/core-framework/include/core/ObjectFactory.h index dda11ab961..2985a752ca 100644 --- a/core-framework/include/core/ObjectFactory.h +++ b/core-framework/include/core/ObjectFactory.h @@ -31,7 +31,7 @@ class ObjectFactoryImpl : public ObjectFactory { ObjectFactoryImpl() = default; - std::string getGroupName() const override { + std::string getModuleName() const override { return group_; } @@ -50,8 +50,8 @@ class DefaultObjectFactory : public ObjectFactoryImpl { : className(core::className()) { } - explicit DefaultObjectFactory(std::string group_name) - : ObjectFactoryImpl(std::move(group_name)), + explicit DefaultObjectFactory(std::string module_name) + : ObjectFactoryImpl(std::move(module_name)), className(core::className()) { } diff --git a/core-framework/include/core/controller/ControllerServiceFactoryImpl.h b/core-framework/include/core/controller/ControllerServiceFactoryImpl.h index a30bd48c65..32f1525a2a 100644 --- a/core-framework/include/core/controller/ControllerServiceFactoryImpl.h +++ b/core-framework/include/core/controller/ControllerServiceFactoryImpl.h @@ -34,13 +34,13 @@ class ControllerServiceFactoryImpl : public ControllerServiceFactory { : class_name_(core::className()) { } - explicit ControllerServiceFactoryImpl(std::string group_name) - : group_name_(std::move(group_name)), + explicit ControllerServiceFactoryImpl(std::string module_name) + : module_name_(std::move(module_name)), class_name_(core::className()) { } - std::string getGroupName() const override { - return group_name_; + std::string getModuleName() const override { + return module_name_; } std::unique_ptr create(ControllerServiceMetadata metadata) override { @@ -52,7 +52,7 @@ class ControllerServiceFactoryImpl : public ControllerServiceFactory { } protected: - std::string group_name_; + std::string module_name_; std::string_view class_name_; }; diff --git a/extension-framework/cpp-extension-lib/include/api/core/Resource.h b/extension-framework/cpp-extension-lib/include/api/core/Resource.h index 60766741cb..8cde8b52b8 100644 --- a/extension-framework/cpp-extension-lib/include/api/core/Resource.h +++ b/extension-framework/cpp-extension-lib/include/api/core/Resource.h @@ -154,12 +154,22 @@ void useControllerServiceClassDefinition(Fn&& fn) { const auto full_name = minifi::core::className(); std::vector class_properties = utils::toProperties(Class::Properties, string_vector_cache); + std::vector provided_interfaces; + if constexpr (requires { Class::ProvidedInterfaces; }) { + provided_interfaces.reserve(Class::ProvidedInterfaces.size()); + for (const auto& iface : Class::ProvidedInterfaces) { + provided_interfaces.push_back(utils::minifiStringView(iface.name)); + } + } MinifiControllerServiceClassDefinition definition{.full_name = utils::minifiStringView(full_name), .description = utils::minifiStringView(Class::Description), .class_properties_count = gsl::narrow(class_properties.size()), .class_properties_ptr = class_properties.data(), + .provided_interfaces_count = gsl::narrow(provided_interfaces.size()), + .provided_interfaces_ptr = provided_interfaces.data(), + .callbacks = MinifiControllerServiceCallbacks{ .create = [](MinifiControllerServiceMetadata metadata) -> MINIFI_OWNED void* { try { @@ -181,6 +191,20 @@ void useControllerServiceClassDefinition(Fn&& fn) { static_cast(self)->disable(); } catch (...) {} }, + .get_interface = [](void* self, MinifiStringView interface_name) -> void* { + try { + const std::string_view name_view{interface_name.data, interface_name.length}; + + if constexpr (requires { Class::ProvidedInterfaces; }) { + for (const auto& iface : Class::ProvidedInterfaces) { + if (iface.name == name_view) { + return iface.cast(self); + } + } + } + return nullptr; + } catch (...) { return nullptr; } + } }}; fn(definition); diff --git a/extensions/stable-api-sandbox/AnimalControllerServiceApis.h b/extensions/stable-api-sandbox/AnimalControllerServiceApis.h new file mode 100644 index 0000000000..eccf373c2d --- /dev/null +++ b/extensions/stable-api-sandbox/AnimalControllerServiceApis.h @@ -0,0 +1,35 @@ +/** + * 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. + */ + +#pragma once + +#include + +namespace org::apache::nifi::minifi::api_sandbox { +class NumberOfLegsControllerApi { + public: + virtual ~NumberOfLegsControllerApi() = default; + virtual uint8_t numberOfLegs() const = 0; +}; + +class CanFlyControllerApi { + public: + virtual ~CanFlyControllerApi() = default; + virtual bool canFly() const = 0; +}; + +} // namespace org::apache::nifi::minifi::api_sandbox diff --git a/extensions/stable-api-sandbox/AnimalControllerServices.cpp b/extensions/stable-api-sandbox/AnimalControllerServices.cpp new file mode 100644 index 0000000000..a8b1efe9d4 --- /dev/null +++ b/extensions/stable-api-sandbox/AnimalControllerServices.cpp @@ -0,0 +1,30 @@ +/** + * 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 "AnimalControllerServices.h" + +#include "api/utils/ProcessorConfigUtils.h" +namespace org::apache::nifi::minifi::api_sandbox { + +MinifiStatus DogController::enableImpl(api::core::ControllerServiceContext& ctx) { + this->has_jetpack_ = ctx.getProperty(HasJetpack) | minifi::utils::andThen(parsing::parseBool) | + minifi::utils::orThrow(fmt::format("Expected parsable bool from \"{}\"", HasJetpack.name)); + + return MINIFI_STATUS_SUCCESS; +} + +} // namespace org::apache::nifi::minifi::api_sandbox diff --git a/extensions/stable-api-sandbox/AnimalControllerServices.h b/extensions/stable-api-sandbox/AnimalControllerServices.h new file mode 100644 index 0000000000..1ab2933969 --- /dev/null +++ b/extensions/stable-api-sandbox/AnimalControllerServices.h @@ -0,0 +1,67 @@ +/** + * 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. + */ + +#pragma once + +#include "AnimalControllerServiceApis.h" +#include "api/core/ControllerServiceImpl.h" +#include "api/utils/Export.h" +#include "core/PropertyDefinitionBuilder.h" +#include "minifi-cpp/core/ProvidedControllerServiceInterface.h" + +namespace org::apache::nifi::minifi::api_sandbox { +class DogController : public api::core::ControllerServiceImpl, public CanFlyControllerApi, public NumberOfLegsControllerApi { + public: + EXTENSIONAPI static constexpr const char* Description = "Test DogController"; + EXTENSIONAPI static constexpr auto HasJetpack = core::PropertyDefinitionBuilder<>::createProperty("Has Jetpack") + .withDescription("Whether or not the dog has a jetpack") + .withDefaultValue("false") + .withValidator(core::StandardPropertyValidators::BOOLEAN_VALIDATOR) + .build(); + + EXTENSIONAPI static constexpr auto Properties = std::to_array({ + HasJetpack, + }); + EXTENSIONAPI static constexpr auto ProvidedInterfaces = + std::to_array({core::createProvidedInterface(), + core::createProvidedInterface()}); + + using ControllerServiceImpl::ControllerServiceImpl; + MinifiStatus enableImpl(api::core::ControllerServiceContext& ctx) override; + + uint8_t numberOfLegs() const override { return 4; } + bool canFly() const override { return has_jetpack_; } + + private: + bool has_jetpack_ = false; +}; + +class DuckController : public api::core::ControllerServiceImpl, public CanFlyControllerApi, public NumberOfLegsControllerApi { + public: + EXTENSIONAPI static constexpr const char* Description = "Test DuckController"; + + EXTENSIONAPI static constexpr std::array Properties = {}; + EXTENSIONAPI static constexpr auto ProvidedInterfaces = + std::to_array({core::createProvidedInterface(), + core::createProvidedInterface()}); + using ControllerServiceImpl::ControllerServiceImpl; + MinifiStatus enableImpl(api::core::ControllerServiceContext&) override { return MINIFI_STATUS_SUCCESS; } + + uint8_t numberOfLegs() const override { return 2; } + bool canFly() const override { return true; } +}; +} // namespace org::apache::nifi::minifi::api_sandbox diff --git a/extensions/stable-api-sandbox/CMakeLists.txt b/extensions/stable-api-sandbox/CMakeLists.txt new file mode 100644 index 0000000000..8afab9a32a --- /dev/null +++ b/extensions/stable-api-sandbox/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# 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. +# + +if (NOT ENABLE_TEST_PROCESSORS) + return() +endif () + +include(${CMAKE_SOURCE_DIR}/extensions/ExtensionHeader.txt) +file(GLOB SOURCES "*.cpp") + +add_minifi_library(minifi-stable-api-sandbox SHARED ${SOURCES}) + +target_link_libraries(minifi-stable-api-sandbox minifi-cpp-extension-lib) + +register_c_api_extension(minifi-stable-api-sandbox "Stable API Sandbox" SANDBOX-EXTENSION "Stable API Sandbox" "extensions/stable-api-sandbox/tests") diff --git a/extensions/stable-api-sandbox/ExtensionInitializer.cpp b/extensions/stable-api-sandbox/ExtensionInitializer.cpp new file mode 100644 index 0000000000..52ed521925 --- /dev/null +++ b/extensions/stable-api-sandbox/ExtensionInitializer.cpp @@ -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. + */ + +#include "AnimalControllerServices.h" +#include "ZooProcessor.h" +#include "api/core/Resource.h" +#include "api/utils/minifi-c-utils.h" + +#define MKSOC(x) #x +#define MAKESTRING(x) MKSOC(x) // NOLINT(cppcoreguidelines-macro-usage) + +namespace minifi = org::apache::nifi::minifi; + +CEXTENSIONAPI const uint32_t MinifiApiVersion = MINIFI_API_VERSION; + +CEXTENSIONAPI void MinifiInitExtension(MinifiExtensionContext* extension_context) { + const MinifiExtensionDefinition extension_definition{.name = minifi::api::utils::minifiStringView(MAKESTRING(EXTENSION_NAME)), + .version = minifi::api::utils::minifiStringView(MAKESTRING(EXTENSION_VERSION)), + .deinit = nullptr, + .user_data = nullptr}; + auto* extension = MinifiRegisterExtension(extension_context, &extension_definition); + minifi::api::core::registerProcessors(extension); + minifi::api::core::registerControllerServices(extension); +} diff --git a/extensions/stable-api-sandbox/ZooProcessor.cpp b/extensions/stable-api-sandbox/ZooProcessor.cpp new file mode 100644 index 0000000000..5157121833 --- /dev/null +++ b/extensions/stable-api-sandbox/ZooProcessor.cpp @@ -0,0 +1,46 @@ +/** + * 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 "ZooProcessor.h" + +#include "AnimalControllerServices.h" +#include "api/core/ProcessContext.h" + +namespace org::apache::nifi::minifi::api_sandbox { + +MinifiStatus ZooProcessor::onTriggerImpl(api::core::ProcessContext& process_context, api::core::ProcessSession& process_session) { + if (const auto can_fly_opaque = process_context.getControllerService(CanFlyService)) { + if (*can_fly_opaque) { + const auto can_fly_controller_name = process_context.getProperty(CanFlyService, nullptr) | utils::orThrow("Should be here"); + const CanFlyControllerApi* can_fly = reinterpret_cast(*can_fly_opaque); + logger_->log_critical("Can {} fly? {}", can_fly_controller_name, can_fly->canFly()); + } + } + if (const auto num_of_legs_opaque = process_context.getControllerService(NumberOfLegsService)) { + if (*num_of_legs_opaque) { + const auto num_of_legs_name = process_context.getProperty(NumberOfLegsService, nullptr) | utils::orThrow("Should be here"); + const NumberOfLegsControllerApi* num_legs = reinterpret_cast(*num_of_legs_opaque); + logger_->log_critical("{} has {} legs", num_of_legs_name, num_legs->numberOfLegs()); + } + } + return ProcessorImpl::onTriggerImpl(process_context, process_session); +} + +MinifiStatus ZooProcessor::onScheduleImpl(api::core::ProcessContext& process_context) { + return ProcessorImpl::onScheduleImpl(process_context); +} +} // namespace org::apache::nifi::minifi::api_sandbox diff --git a/extensions/stable-api-sandbox/ZooProcessor.h b/extensions/stable-api-sandbox/ZooProcessor.h new file mode 100644 index 0000000000..98ac0b8f59 --- /dev/null +++ b/extensions/stable-api-sandbox/ZooProcessor.h @@ -0,0 +1,63 @@ +/** + * 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. + */ + +#pragma once + +#include "AnimalControllerServiceApis.h" +#include "api/core/ProcessorImpl.h" +#include "api/utils/Export.h" +#include "core/PropertyDefinitionBuilder.h" +#include "minifi-cpp/core/Annotation.h" + +namespace org::apache::nifi::minifi::api_sandbox { + +class ZooProcessor : public api::core::ProcessorImpl { + public: + EXTENSIONAPI static constexpr const char* Description = "Test ZooProcessor"; + + EXTENSIONAPI static constexpr auto CanFlyService = core::PropertyDefinitionBuilder<>::createProperty("Can fly service") + .withDescription("Test CanFlyService") + .isRequired(true) + .withAllowedTypes() + .build(); + + EXTENSIONAPI static constexpr auto NumberOfLegsService = core::PropertyDefinitionBuilder<>::createProperty("Number of legs service") + .withDescription("Test NumberOfLegsService") + .isRequired(true) + .withAllowedTypes() + .build(); + + EXTENSIONAPI static constexpr auto Properties = std::to_array({ + CanFlyService, + NumberOfLegsService, + }); + EXTENSIONAPI static constexpr auto Success = core::RelationshipDefinition{"success", "success"}; + EXTENSIONAPI static constexpr auto Failure = core::RelationshipDefinition{"failure", "failure"}; + EXTENSIONAPI static constexpr auto Relationships = std::array{Success, Failure}; + EXTENSIONAPI static constexpr bool SupportsDynamicProperties = false; + EXTENSIONAPI static constexpr bool SupportsDynamicRelationships = false; + EXTENSIONAPI static constexpr core::annotation::Input InputRequirement = core::annotation::Input::INPUT_FORBIDDEN; + EXTENSIONAPI static constexpr bool IsSingleThreaded = true; + + using ProcessorImpl::ProcessorImpl; + + protected: + MinifiStatus onTriggerImpl(api::core::ProcessContext&, api::core::ProcessSession&) override; + MinifiStatus onScheduleImpl(api::core::ProcessContext&) override; +}; + +} // namespace org::apache::nifi::minifi::api_sandbox diff --git a/extensions/stable-api-sandbox/tests/CMakeLists.txt b/extensions/stable-api-sandbox/tests/CMakeLists.txt new file mode 100644 index 0000000000..225e7acb6b --- /dev/null +++ b/extensions/stable-api-sandbox/tests/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# 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. +# + +file(GLOB SOURCES "*.cpp") + +SET(EXTENSIONS_TEST_COUNT 0) +FOREACH (testfile ${SOURCES}) + get_filename_component(testfilename "${testfile}" NAME_WE) + add_minifi_executable(${testfilename} "${testfile}") + target_include_directories(${testfilename} BEFORE PRIVATE "${CMAKE_SOURCE_DIR}/libminifi/include") + target_include_directories(${testfilename} BEFORE PRIVATE "${CMAKE_SOURCE_DIR}/extensions/stable-api-sandbox") + createTests(${testfilename}) + target_link_libraries(${testfilename} Catch2WithMain) + target_link_libraries(${testfilename} minifi-stable-api-sandbox) + target_link_libraries(${testfilename} libminifi-c-unittest) + + MATH(EXPR EXTENSIONS_TEST_COUNT "${EXTENSIONS_TEST_COUNT}+1") + add_test(NAME ${testfilename} COMMAND ${testfilename} WORKING_DIRECTORY ${TEST_DIR}) +ENDFOREACH () diff --git a/extensions/stable-api-sandbox/tests/ZooTests.cpp b/extensions/stable-api-sandbox/tests/ZooTests.cpp new file mode 100644 index 0000000000..5f49e1d1c6 --- /dev/null +++ b/extensions/stable-api-sandbox/tests/ZooTests.cpp @@ -0,0 +1,71 @@ +/** + * + * 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 "../AnimalControllerServices.h" +#include "CProcessorTestUtils.h" +#include "ZooProcessor.h" +#include "api/core/Resource.h" +#include "minifi-cpp/core/FlowFile.h" +#include "unit/Catch.h" +#include "unit/SingleProcessorTestController.h" +#include "unit/TestBase.h" +#include "unit/TestUtils.h" +#include "utils/CProcessor.h" + +namespace org::apache::nifi::minifi::api_sandbox::test { +TEST_CASE("ZooTest") { + minifi::test::SingleProcessorTestController controller(minifi::test::utils::make_custom_c_processor( + core::ProcessorMetadata{utils::Identifier{}, "ZooProcessor", logging::LoggerFactory::getLogger()})); + const auto dog_with_jetpack = minifi::test::utils::make_custom_c_controller_service(core::ControllerServiceMetadata{ + utils::Identifier{}, + "DogController", + logging::LoggerFactory::getLogger()}); + auto dog_with_jetpack_node = controller.plan->addController("dog_with_jetpack", dog_with_jetpack); + CHECK(dog_with_jetpack->setProperty(DogController::HasJetpack.name, "true")); + + const auto duck = minifi::test::utils::make_custom_c_controller_service(core::ControllerServiceMetadata{utils::Identifier{}, + "DuckController", + logging::LoggerFactory::getLogger()}); + auto duck_node = controller.plan->addController("duck", duck); + + { + CHECK(controller.getProcessor()->setProperty(ZooProcessor::CanFlyService.name, "dog_with_jetpack")); + CHECK(controller.getProcessor()->setProperty(ZooProcessor::NumberOfLegsService.name, "duck")); + controller.trigger(); + CHECK(LogTestController::getInstance() + .contains("[org::apache::nifi::minifi::api_sandbox::ZooProcessor] [critical] Can dog_with_jetpack fly? true")); + CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_sandbox::ZooProcessor] [critical] duck has 2 legs")); + } + { + LogTestController::getInstance().clear(); + CHECK(controller.getProcessor()->setProperty(ZooProcessor::CanFlyService.name, "duck")); + CHECK(controller.getProcessor()->setProperty(ZooProcessor::NumberOfLegsService.name, "dog_with_jetpack")); + controller.trigger(); + CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_sandbox::ZooProcessor] [critical] Can duck fly? true")); + CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_sandbox::ZooProcessor] [critical] dog_with_jetpack has 4 legs")); + } + { + LogTestController::getInstance().clear(); + CHECK(controller.getProcessor()->setProperty(ZooProcessor::CanFlyService.name, "duck")); + CHECK(controller.getProcessor()->setProperty(ZooProcessor::NumberOfLegsService.name, "invalid")); + controller.trigger(); + CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_sandbox::ZooProcessor] [critical] Can duck fly? true")); + } +} + +} // namespace org::apache::nifi::minifi::api_sandbox::test diff --git a/libminifi/include/core/state/nodes/AgentInformation.h b/libminifi/include/core/state/nodes/AgentInformation.h index d76846b8ea..89b238202f 100644 --- a/libminifi/include/core/state/nodes/AgentInformation.h +++ b/libminifi/include/core/state/nodes/AgentInformation.h @@ -34,8 +34,6 @@ namespace org::apache::nifi::minifi::state::response { -#define GROUP_STR "org.apache.nifi.minifi" - std::vector serializeComponentManifest(const Components& components); class Bundles : public DeviceInformation { diff --git a/libminifi/src/core/ClassLoader.cpp b/libminifi/src/core/ClassLoader.cpp index 463b2a2e49..530ec2944a 100644 --- a/libminifi/src/core/ClassLoader.cpp +++ b/libminifi/src/core/ClassLoader.cpp @@ -48,7 +48,7 @@ class ClassLoaderImpl : public ClassLoader { void unregisterClass(const std::string& clazz) override; - [[nodiscard]] std::optional getGroupForClass(const std::string &class_name) const override; + [[nodiscard]] std::optional getModuleForClass(std::string_view class_name) const override; [[nodiscard]] std::unique_ptr instantiate(const std::string &class_name, const std::string &name, std::function filter) override; @@ -61,7 +61,7 @@ class ClassLoaderImpl : public ClassLoader { ~ClassLoaderImpl() override = default; private: - std::map> loaded_factories_; + std::map, std::less<>> loaded_factories_; std::map class_loaders_; mutable std::mutex internal_mutex_; std::shared_ptr logger_; @@ -128,7 +128,7 @@ class ProcessorFactoryWrapper : public ObjectFactoryImpl { return new Processor(name, uuid, factory_->create({.uuid = uuid, .name = name, .logger = std::move(logger)})); } - [[nodiscard]] std::string getGroupName() const override { + [[nodiscard]] std::string getModuleName() const override { return factory_->getGroupName(); } @@ -143,7 +143,7 @@ class ProcessorFactoryWrapper : public ObjectFactoryImpl { class ControllerServiceFactoryWrapper : public ObjectFactoryImpl { public: explicit ControllerServiceFactoryWrapper(std::unique_ptr factory) - : ObjectFactoryImpl(factory->getGroupName()), + : ObjectFactoryImpl(factory->getModuleName()), factory_(std::move(factory)) {} [[nodiscard]] std::unique_ptr create(const std::string &name) override { @@ -163,8 +163,8 @@ class ControllerServiceFactoryWrapper : public ObjectFactoryImpl { return new controller::ControllerService(name, uuid, factory_->create({.uuid = uuid, .name = name, .logger = std::move(logger)})); } - [[nodiscard]] std::string getGroupName() const override { - return factory_->getGroupName(); + [[nodiscard]] std::string getModuleName() const override { + return factory_->getModuleName(); } [[nodiscard]] std::string getClassName() override { @@ -194,17 +194,17 @@ void ClassLoaderImpl::unregisterClass(const std::string& clazz) { } } -std::optional ClassLoaderImpl::getGroupForClass(const std::string &class_name) const { +std::optional ClassLoaderImpl::getModuleForClass(const std::string_view class_name) const { std::lock_guard lock(internal_mutex_); for (const auto& child_loader : class_loaders_) { - std::optional group = child_loader.second.getGroupForClass(class_name); + std::optional group = child_loader.second.getModuleForClass(class_name); if (group) { return group; } } auto factory = loaded_factories_.find(class_name); if (factory != loaded_factories_.end()) { - return factory->second->getGroupName(); + return factory->second->getModuleName(); } return {}; } diff --git a/libminifi/src/core/state/nodes/AgentInformation.cpp b/libminifi/src/core/state/nodes/AgentInformation.cpp index 498dada875..f3a31d7bbe 100644 --- a/libminifi/src/core/state/nodes/AgentInformation.cpp +++ b/libminifi/src/core/state/nodes/AgentInformation.cpp @@ -17,12 +17,17 @@ */ #include "core/state/nodes/AgentInformation.h" -#include "minifi-cpp/agent/agent_version.h" -#include "core/Resource.h" #include "core/ClassLoader.h" -#include "utils/OsUtils.h" +#include "core/Resource.h" #include "core/state/nodes/SchedulingNodes.h" #include "core/state/nodes/SupportedOperations.h" +#include "minifi-c/minifi-c.h" +#include "minifi-cpp/agent/agent_version.h" +#include "minifi-cpp/controllers/ProxyConfigurationServiceInterface.h" +#include "minifi-cpp/controllers/RecordSetReader.h" +#include "minifi-cpp/controllers/RecordSetWriter.h" +#include "minifi-cpp/controllers/SSLContextServiceInterface.h" +#include "utils/OsUtils.h" namespace org::apache::nifi::minifi::state::response { @@ -30,33 +35,73 @@ utils::ProcessCpuUsageTracker AgentStatus::cpu_load_tracker_; std::mutex AgentStatus::cpu_load_tracker_mutex_; namespace { + +std::string allowedTypeGroupName(const std::string_view allowed_type) { + constexpr std::string_view APACHE_GROUP_STR = "org.apache.nifi.minifi"; + if (allowed_type.starts_with(APACHE_GROUP_STR)) { + return std::string{APACHE_GROUP_STR}; + } + return std::string{allowed_type.substr(0, allowed_type.find_last_of('.'))}; +} + +constexpr std::string_view shortNameFromFullName(std::string_view sv) { + auto pos = sv.rfind('.'); + return (pos == std::string_view::npos) ? sv : sv.substr(pos + 1); +} + +bool minifiSystemAllowedType(const std::string_view allowed_type) { + return allowed_type == controllers::SSLContextServiceInterface::ProvidesApi.type + || allowed_type == core::RecordSetWriter::ProvidesApi.type + || allowed_type == core::RecordSetReader::ProvidesApi.type + || allowed_type == controllers::ProxyConfigurationServiceInterface::ProvidesApi.type; +} + +std::string allowedTypeArtifactName(const std::string_view allowed_type, const std::string_view prop_owner_type) { + + const std::string minifi_system_artifact = "minifi-system"; + if (minifiSystemAllowedType(allowed_type)) { + return minifi_system_artifact; + } + + auto& class_loader = core::ClassLoader::getDefaultClassLoader(); + + if (auto allowed_type_artifact = class_loader.getModuleForClass(shortNameFromFullName(allowed_type))) { + return *allowed_type_artifact; + } + if (auto prop_owner_artifact = class_loader.getModuleForClass(shortNameFromFullName(prop_owner_type))) { + return *prop_owner_artifact; + } + + return minifi_system_artifact; +} + void serializeClassDescription(const std::vector& descriptions, const std::string& name, SerializedResponseNode& response) { if (descriptions.empty()) { return; } SerializedResponseNode type{.name = name, .array = true}; std::vector serialized; - for (const auto& group : descriptions) { - SerializedResponseNode desc{.name = group.full_name_}; + for (const auto& class_description : descriptions) { + SerializedResponseNode desc{.name = class_description.full_name_}; - if (!group.class_properties_.empty()) { + if (!class_description.class_properties_.empty()) { SerializedResponseNode props{.name = "propertyDescriptors"}; - for (auto&& prop : group.class_properties_) { + for (auto&& prop : class_description.class_properties_) { SerializedResponseNode child = {.name = prop.getName()}; const auto &allowed_types = prop.getAllowedTypes(); if (!allowed_types.empty()) { - SerializedResponseNode allowed_type; - allowed_type.name = "typeProvidedByValue"; - for (const auto &type : allowed_types) { - std::string class_name = utils::string::split(type, "::").back(); - std::string typeClazz = type; + SerializedResponseNode allowed_type_node; + allowed_type_node.name = "typeProvidedByValue"; + for (const auto &allowed_type : allowed_types) { + std::string class_name = utils::string::split(allowed_type, "::").back(); + std::string typeClazz = allowed_type; utils::string::replaceAll(typeClazz, "::", "."); - allowed_type.children.push_back({.name = "type", .value = typeClazz}); - allowed_type.children.push_back({.name = "group", .value = GROUP_STR}); - allowed_type.children.push_back({.name = "artifact", .value = core::ClassLoader::getDefaultClassLoader().getGroupForClass(class_name).value_or("minifi-system")}); + allowed_type_node.children.push_back({.name = "type", .value = typeClazz}); + allowed_type_node.children.push_back({.name = "group", .value = allowedTypeGroupName(typeClazz)}); + allowed_type_node.children.push_back({.name = "artifact", .value = allowedTypeArtifactName(typeClazz, class_description.full_name_)}); } - child.children.push_back(allowed_type); + child.children.push_back(allowed_type_node); } child.children.push_back({.name = "name", .value = prop.getName()}); @@ -100,12 +145,12 @@ void serializeClassDescription(const std::vector& descriptions } // only for processors - if (!group.class_relationships_.empty()) { - desc.children.push_back({.name = "inputRequirement", .value = group.inputRequirement_}); - desc.children.push_back({.name = "isSingleThreaded", .value = group.isSingleThreaded_}); + if (!class_description.class_relationships_.empty()) { + desc.children.push_back({.name = "inputRequirement", .value = class_description.inputRequirement_}); + desc.children.push_back({.name = "isSingleThreaded", .value = class_description.isSingleThreaded_}); SerializedResponseNode relationships{.name = "supportedRelationships", .array = true}; - for (const auto &relationship : group.class_relationships_) { + for (const auto &relationship : class_description.class_relationships_) { SerializedResponseNode child{.name = "supportedRelationships"}; child.children.push_back({.name = "name", .value = relationship.getName()}); child.children.push_back({.name = "description", .value = relationship.getDescription()}); @@ -115,13 +160,13 @@ void serializeClassDescription(const std::vector& descriptions desc.children.push_back(relationships); } - desc.children.push_back({.name = "typeDescription", .value = group.description_}); - desc.children.push_back({.name = "supportsDynamicRelationships", .value = group.supports_dynamic_relationships_}); - desc.children.push_back({.name = "supportsDynamicProperties", .value = group.supports_dynamic_properties_}); - desc.children.push_back({.name = "type", .value = group.full_name_}); - if (!group.api_implementations.empty()) { + desc.children.push_back({.name = "typeDescription", .value = class_description.description_}); + desc.children.push_back({.name = "supportsDynamicRelationships", .value = class_description.supports_dynamic_relationships_}); + desc.children.push_back({.name = "supportsDynamicProperties", .value = class_description.supports_dynamic_properties_}); + desc.children.push_back({.name = "type", .value = class_description.full_name_}); + if (!class_description.api_implementations.empty()) { SerializedResponseNode provided_api_impls{.name = "providedApiImplementations", .array = true}; - for (const auto& api_implementation : group.api_implementations) { + for (const auto& api_implementation : class_description.api_implementations) { SerializedResponseNode child{.name = std::string(api_implementation.type)}; child.children.push_back({.name = "artifact", .value = std::string(api_implementation.artifact)}); child.children.push_back({.name = "group", .value = std::string(api_implementation.group)}); @@ -155,12 +200,12 @@ std::vector Bundles::serialize() { if (serialized_components[0].children.empty()) { continue; } - + std::string serialized_comp_name = serialized_components[0].name; SerializedResponseNode serialized_bundle { .name = "bundles", .children = { serialized_components[0], - {.name = "group", .value = GROUP_STR}, + {.name = "group", .value = allowedTypeGroupName(serialized_comp_name)}, {.name = "artifact", .value = bundle.name}, {.name = "version", .value = bundle.version}, } diff --git a/libminifi/src/minifi-c.cpp b/libminifi/src/minifi-c.cpp index ea50f1bf7a..21c4a74d68 100644 --- a/libminifi/src/minifi-c.cpp +++ b/libminifi/src/minifi-c.cpp @@ -164,7 +164,7 @@ class CControllerServiceFactory : public minifi::core::controller::ControllerSer return std::make_unique(class_description_, std::move(metadata)); } - [[nodiscard]] std::string getGroupName() const override { return group_name_; } + [[nodiscard]] std::string getModuleName() const override { return group_name_; } [[nodiscard]] std::string getClassName() const override { return class_name_; } @@ -277,15 +277,23 @@ void useCControllerServiceClassDescription(const MinifiControllerServiceClassDef auto name_segments = minifi::utils::string::split(toStringView(class_description.full_name), "::"); gsl_Assert(!name_segments.empty()); - minifi::ClassDescription description{ - .type_ = minifi::ResourceType::ControllerService, - .short_name_ = name_segments.back(), - .full_name_ = minifi::utils::string::join(".", name_segments), - .description_ = toString(class_description.description), - .class_properties_ = properties, + std::vector implements_apis; + implements_apis.reserve(class_description.provided_interfaces_count); + for (size_t i = 0; i < class_description.provided_interfaces_count; ++i) { + auto api_segments = string::split(toStringView(class_description.provided_interfaces_ptr[i]), "::"); + implements_apis.push_back(core::ControllerServiceType::minifiSystemControllerServiceType(string::join(".", api_segments))); + } + + ClassDescription description{ + .type_ = ResourceType::ControllerService, + .short_name_ = name_segments.back(), + .full_name_ = string::join(".", name_segments), + .description_ = toString(class_description.description), + .class_properties_ = properties, + .api_implementations = implements_apis, }; - minifi::utils::CControllerServiceClassDescription c_class_description{ + CControllerServiceClassDescription c_class_description{ .full_name = toString(class_description.full_name), .class_properties = properties, .callbacks = class_description.callbacks @@ -613,6 +621,14 @@ MinifiStatus MinifiProcessContextGetControllerServiceFromProperty( *controller_service_out = static_cast(c_controller_service->getImpl()); return MINIFI_STATUS_SUCCESS; } + if (class_description.callbacks.get_interface) { + const auto interface_res = class_description.callbacks.get_interface(c_controller_service->getImpl(), controller_service_type); + if (!interface_res) { + return MINIFI_STATUS_VALIDATION_FAILED; + } + *controller_service_out = static_cast(interface_res); + return MINIFI_STATUS_SUCCESS; + } } return MINIFI_STATUS_VALIDATION_FAILED; } diff --git a/libminifi/test/unit/ComponentManifestTests.cpp b/libminifi/test/unit/ComponentManifestTests.cpp index 7bc08750fc..e9ddf16c00 100644 --- a/libminifi/test/unit/ComponentManifestTests.cpp +++ b/libminifi/test/unit/ComponentManifestTests.cpp @@ -112,7 +112,7 @@ TEST_CASE("Manifest indicates property type requirement") { auto& type = get(*prop_it, "typeProvidedByValue"); REQUIRE(get(type, "type").value == "test.apple.ExampleService"); - REQUIRE(get(type, "group").value == GROUP_STR); // fix string + REQUIRE(get(type, "group").value == "org.apache.nifi.minifi"); REQUIRE(get(type, "artifact").value == "minifi-system"); } diff --git a/minifi-api/common/include/minifi-cpp/core/ProvidedControllerServiceInterface.h b/minifi-api/common/include/minifi-cpp/core/ProvidedControllerServiceInterface.h new file mode 100644 index 0000000000..0f28ff05a4 --- /dev/null +++ b/minifi-api/common/include/minifi-cpp/core/ProvidedControllerServiceInterface.h @@ -0,0 +1,34 @@ +/** + * 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. + */ +#pragma once + +#include +#include "core/ClassName.h" + +namespace org::apache::nifi::minifi::core { +struct ProvidedInterface { + std::string_view name; + void* (*cast)(void* instance); +}; + +template +constexpr ProvidedInterface createProvidedInterface() { + return ProvidedInterface{className(), [](void* instance) -> void* { + return static_cast(static_cast(instance)); + }}; +} +} // namespace org::apache::nifi::minifi::core diff --git a/minifi-api/include/minifi-c/minifi-c.h b/minifi-api/include/minifi-c/minifi-c.h index b54375670f..54aa5fba00 100644 --- a/minifi-api/include/minifi-c/minifi-c.h +++ b/minifi-api/include/minifi-c/minifi-c.h @@ -186,6 +186,7 @@ typedef struct MinifiControllerServiceCallbacks { void(*destroy)(MINIFI_OWNED void*); MinifiStatus(*enable)(void*, MinifiControllerServiceContext*); void(*disable)(void*); + void* (*get_interface)(void* ctx, MinifiStringView interface_type); } MinifiControllerServiceCallbacks; typedef struct MinifiProcessorClassDefinition { @@ -212,6 +213,8 @@ typedef struct MinifiControllerServiceClassDefinition { MinifiStringView description; size_t class_properties_count; const MinifiPropertyDefinition* class_properties_ptr; + size_t provided_interfaces_count; + const MinifiStringView* provided_interfaces_ptr; MinifiControllerServiceCallbacks callbacks; } MinifiControllerServiceClassDefinition; diff --git a/minifi-api/include/minifi-cpp/controllers/RecordSetReader.h b/minifi-api/include/minifi-cpp/controllers/RecordSetReader.h index c05d20396c..9be0b6009f 100644 --- a/minifi-api/include/minifi-cpp/controllers/RecordSetReader.h +++ b/minifi-api/include/minifi-cpp/controllers/RecordSetReader.h @@ -19,10 +19,7 @@ #include "minifi-cpp/core/controller/ControllerServiceHandle.h" #include "minifi-cpp/core/ControllerServiceTypeDefinition.h" #include "minifi-cpp/core/Record.h" -#include "utils/Enum.h" -#include "utils/ProcessorConfigUtils.h" #include "minifi-cpp/io/InputStream.h" -#include "minifi-cpp/agent/agent_version.h" namespace org::apache::nifi::minifi::core { diff --git a/minifi-api/include/minifi-cpp/core/ClassLoader.h b/minifi-api/include/minifi-cpp/core/ClassLoader.h index 559ef00484..c377bca13f 100644 --- a/minifi-api/include/minifi-cpp/core/ClassLoader.h +++ b/minifi-api/include/minifi-cpp/core/ClassLoader.h @@ -72,7 +72,7 @@ class ClassLoader { virtual void unregisterClass(const std::string& clazz) = 0; - [[nodiscard]] virtual std::optional getGroupForClass(const std::string &class_name) const = 0; + [[nodiscard]] virtual std::optional getModuleForClass(std::string_view class_name) const = 0; [[nodiscard]] virtual std::unique_ptr instantiate(const std::string &class_name, const std::string &name, std::function filter) = 0; diff --git a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h b/minifi-api/include/minifi-cpp/core/ControllerServiceType.h index 6d5b39de3d..f45a8e3fb4 100644 --- a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h +++ b/minifi-api/include/minifi-cpp/core/ControllerServiceType.h @@ -1,5 +1,5 @@ /** -* Licensed to the Apache Software Foundation (ASF) under one or more + * 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 @@ -17,6 +17,7 @@ #pragma once #include + #include "minifi-cpp/core/ControllerServiceTypeDefinition.h" namespace org::apache::nifi::minifi::core { @@ -27,9 +28,19 @@ struct ControllerServiceType { std::string type; ControllerServiceType(const ControllerServiceTypeDefinition& definition) // NOLINT(runtime/explicit) - : artifact(definition.artifact), - group(definition.group), - type(definition.type) {} + : artifact(definition.artifact), + group(definition.group), + type(definition.type) {} + + static ControllerServiceType minifiSystemControllerServiceType(std::string type) { + return ControllerServiceType("minifi-system", "org.apache.nifi.minifi", std::move(type)); + } + + private: + ControllerServiceType(std::string artifact, std::string group, std::string type) + : artifact(std::move(artifact)), + group(std::move(group)), + type(std::move(type)) {} }; } // namespace org::apache::nifi::minifi::core diff --git a/minifi-api/include/minifi-cpp/core/ObjectFactory.h b/minifi-api/include/minifi-cpp/core/ObjectFactory.h index 8058151eed..8ff5039a9f 100644 --- a/minifi-api/include/minifi-cpp/core/ObjectFactory.h +++ b/minifi-api/include/minifi-cpp/core/ObjectFactory.h @@ -30,7 +30,7 @@ class ObjectFactory { virtual gsl::owner createRaw(const std::string& /*name*/) = 0; virtual std::unique_ptr create(const std::string& /*name*/, const utils::Identifier& /*uuid*/) = 0; virtual gsl::owner createRaw(const std::string& /*name*/, const utils::Identifier& /*uuid*/) = 0; - virtual std::string getGroupName() const = 0; + virtual std::string getModuleName() const = 0; virtual std::string getClassName() = 0; virtual ~ObjectFactory() = default; diff --git a/minifi-api/include/minifi-cpp/core/controller/ControllerServiceFactory.h b/minifi-api/include/minifi-cpp/core/controller/ControllerServiceFactory.h index aafc571dcd..17b8637ebf 100644 --- a/minifi-api/include/minifi-cpp/core/controller/ControllerServiceFactory.h +++ b/minifi-api/include/minifi-cpp/core/controller/ControllerServiceFactory.h @@ -30,7 +30,7 @@ class ControllerServiceApi; class ControllerServiceFactory { public: virtual std::unique_ptr create(ControllerServiceMetadata metadata) = 0; - virtual std::string getGroupName() const = 0; + virtual std::string getModuleName() const = 0; virtual std::string getClassName() const = 0; virtual ~ControllerServiceFactory() = default;