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

martinzink pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 45eca0ceb MINIFICPP-2816 Add controller_service::api_implementations 
to C api (#2176)
45eca0ceb is described below

commit 45eca0ceb2d4ccb6d23656aac75591228710f666
Author: Martin Zink <[email protected]>
AuthorDate: Wed Jun 24 14:25:51 2026 +0200

    MINIFICPP-2816 Add controller_service::api_implementations to C api (#2176)
---
 .clang-format                                      |   4 +-
 .../ubuntu_22_04_clang_arm_manifest.json           |  93 +++++++++++++++++++
 .github/workflows/ci.yml                           |   6 +-
 core-framework/include/core/ObjectFactory.h        |  12 +--
 .../core/controller/ControllerServiceFactoryImpl.h |  10 +--
 .../cpp-extension-lib/include/api/core/Resource.h  |  23 +++++
 .../AnimalControllerServiceApis.h                  |  28 +++---
 .../AnimalControllerServices.cpp                   |  25 +++---
 .../stable-api-testing/AnimalControllerServices.h  |  69 ++++++++++++++
 extensions/stable-api-testing/CMakeLists.txt       |  31 +++++++
 .../stable-api-testing/ExtensionInitializer.cpp    |  38 ++++++++
 extensions/stable-api-testing/ZooProcessor.cpp     |  46 ++++++++++
 extensions/stable-api-testing/ZooProcessor.h       |  65 ++++++++++++++
 extensions/stable-api-testing/tests/CMakeLists.txt |  35 ++++++++
 extensions/stable-api-testing/tests/ZooTests.cpp   |  69 ++++++++++++++
 .../standard-processors/controllers/XMLReader.cpp  |   3 +-
 .../include/core/state/nodes/AgentInformation.h    |   2 -
 libminifi/src/core/ClassLoader.cpp                 |  66 +++++++-------
 .../src/core/state/nodes/AgentInformation.cpp      | 100 +++++++++++++++------
 libminifi/src/minifi-c.cpp                         |  38 +++++---
 libminifi/test/unit/ComponentManifestTests.cpp     |  62 ++++++++++---
 .../core/ProvidedControllerServiceInterface.h      |  48 ++++++++++
 minifi-api/include/minifi-c/minifi-c.h             |   3 +
 .../minifi-cpp/controllers/RecordSetReader.h       |   9 +-
 minifi-api/include/minifi-cpp/core/ClassLoader.h   |   2 +-
 .../minifi-cpp/core/ControllerServiceType.h        |  19 +++-
 minifi-api/include/minifi-cpp/core/ObjectFactory.h |   2 +-
 .../core/controller/ControllerServiceFactory.h     |   2 +-
 28 files changed, 764 insertions(+), 146 deletions(-)

diff --git a/.clang-format b/.clang-format
index 1207baf97..013a12279 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,6 +1,6 @@
 BasedOnStyle: Google
 AccessModifierOffset: -1
-AlignAfterOpenBracket: DontAlign
+AlignAfterOpenBracket: false
 AlignConsecutiveAssignments: None
 AlignOperands: DontAlign
 AllowAllParametersOfDeclarationOnNextLine: true
@@ -31,7 +31,7 @@ MaxEmptyLinesToKeep: 1
 NamespaceIndentation: None
 ObjCSpaceAfterProperty: false
 ObjCSpaceBeforeProtocolList: false
-PenaltyIndentedWhitespace: 1
+PenaltyIndentedWhitespace: 10
 PointerAlignment: Left
 ReferenceAlignment: Left
 ReflowComments: true
diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json 
b/.github/references/ubuntu_22_04_clang_arm_manifest.json
index 976ba021b..8cabb5440 100644
--- a/.github/references/ubuntu_22_04_clang_arm_manifest.json
+++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json
@@ -10860,6 +10860,52 @@
                 "supportsDynamicRelationships": "false",
                 "supportsDynamicProperties": "true",
                 "type": "org.apache.nifi.minifi.processors.UpdateAttribute"
+            },
+            {
+                "propertyDescriptors": {
+                    "Can fly service": {
+                        "typeProvidedByValue": {
+                            "type": 
"org.apache.nifi.minifi.api_testing.CanFlyControllerApi",
+                            "group": "org.apache.nifi.minifi",
+                            "artifact": "minifi-stable-api-testing"
+                        },
+                        "name": "Can fly service",
+                        "description": "Test CanFlyService",
+                        "validator": "VALID",
+                        "required": "true",
+                        "sensitive": "false",
+                        "expressionLanguageScope": "NONE"
+                    },
+                    "Number of legs service": {
+                        "typeProvidedByValue": {
+                            "type": 
"org.apache.nifi.minifi.api_testing.NumberOfLegsControllerApi",
+                            "group": "org.apache.nifi.minifi",
+                            "artifact": "minifi-stable-api-testing"
+                        },
+                        "name": "Number of legs service",
+                        "description": "Test NumberOfLegsService",
+                        "validator": "VALID",
+                        "required": "true",
+                        "sensitive": "false",
+                        "expressionLanguageScope": "NONE"
+                    }
+                },
+                "inputRequirement": "INPUT_FORBIDDEN",
+                "isSingleThreaded": "true",
+                "supportedRelationships": [
+                    {
+                        "name": "failure",
+                        "description": "failure"
+                    },
+                    {
+                        "name": "success",
+                        "description": "success"
+                    }
+                ],
+                "typeDescription": "Test ZooProcessor",
+                "supportsDynamicRelationships": "false",
+                "supportsDynamicProperties": "false",
+                "type": "org.apache.nifi.minifi.api_testing.ZooProcessor"
             }
         ],
         "controllerServices": [
@@ -11019,6 +11065,53 @@
                 "supportsDynamicProperties": "false",
                 "type": 
"org.apache.nifi.minifi.couchbase.controllers.CouchbaseClusterService"
             },
+            {
+                "propertyDescriptors": {
+                    "Has Jetpack": {
+                        "name": "Has Jetpack",
+                        "description": "Whether or not the dog has a jetpack",
+                        "validator": "BOOLEAN_VALIDATOR",
+                        "required": "false",
+                        "sensitive": "false",
+                        "expressionLanguageScope": "NONE",
+                        "defaultValue": "false"
+                    }
+                },
+                "typeDescription": "Test DogController",
+                "supportsDynamicRelationships": "false",
+                "supportsDynamicProperties": "false",
+                "type": "org.apache.nifi.minifi.api_testing.DogController",
+                "providedApiImplementations": [
+                    {
+                        "artifact": "minifi-system",
+                        "group": "org.apache.nifi.minifi",
+                        "type": 
"org.apache.nifi.minifi.api_testing.CanFlyControllerApi"
+                    },
+                    {
+                        "artifact": "minifi-system",
+                        "group": "org.apache.nifi.minifi",
+                        "type": 
"org.apache.nifi.minifi.api_testing.NumberOfLegsControllerApi"
+                    }
+                ]
+            },
+            {
+                "typeDescription": "Test DuckController",
+                "supportsDynamicRelationships": "false",
+                "supportsDynamicProperties": "false",
+                "type": "org.apache.nifi.minifi.api_testing.DuckController",
+                "providedApiImplementations": [
+                    {
+                        "artifact": "minifi-system",
+                        "group": "org.apache.nifi.minifi",
+                        "type": 
"org.apache.nifi.minifi.api_testing.CanFlyControllerApi"
+                    },
+                    {
+                        "artifact": "minifi-system",
+                        "group": "org.apache.nifi.minifi",
+                        "type": 
"org.apache.nifi.minifi.api_testing.NumberOfLegsControllerApi"
+                    }
+                ]
+            },
             {
                 "propertyDescriptors": {
                     "API Key": {
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8fa667fa8..c1d5f8896 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,7 +52,7 @@ jobs:
         -DENABLE_SFTP=ON
         -DENABLE_SPLUNK=ON
         -DENABLE_SQL=ON
-        -DENABLE_TEST_PROCESSORS=OFF
+        -DENABLE_TEST_PROCESSORS=ON
         -DFORCE_COLORED_OUTPUT=OFF
         -DLIBC_STATIC=OFF
         -DMINIFI_ADVANCED_ASAN_BUILD=OFF
@@ -159,7 +159,7 @@ jobs:
         -DENABLE_SMB=ON
         -DENABLE_SPLUNK=ON
         -DENABLE_SQL=ON
-        -DENABLE_TEST_PROCESSORS=OFF
+        -DENABLE_TEST_PROCESSORS=ON
         -DENABLE_WEL=ON
         -DFORCE_COLORED_OUTPUT=ON
         -DINSTALLER_MERGE_MODULES=OFF
@@ -261,7 +261,7 @@ jobs:
         -DENABLE_SPLUNK=ON
         -DENABLE_SQL=ON
         -DENABLE_SYSTEMD=ON
-        -DENABLE_TEST_PROCESSORS=OFF
+        -DENABLE_TEST_PROCESSORS=ON
         -DFORCE_COLORED_OUTPUT=ON
         -DMINIFI_FAIL_ON_WARNINGS=ON
         -DPORTABLE=ON
diff --git a/core-framework/include/core/ObjectFactory.h 
b/core-framework/include/core/ObjectFactory.h
index dda11ab96..e8ebb43dd 100644
--- a/core-framework/include/core/ObjectFactory.h
+++ b/core-framework/include/core/ObjectFactory.h
@@ -27,16 +27,16 @@ namespace org::apache::nifi::minifi::core {
 
 class ObjectFactoryImpl : public ObjectFactory {
  public:
-  explicit ObjectFactoryImpl(std::string group) : group_(std::move(group)) {}
+  explicit ObjectFactoryImpl(std::string module_name) : 
module_name_(std::move(module_name)) {}
 
   ObjectFactoryImpl() = default;
 
-  std::string getGroupName() const override {
-    return group_;
+  std::string getModuleName() const override {
+    return module_name_;
   }
 
  protected:
-  std::string group_;
+  std::string module_name_;
 };
 
 /**
@@ -50,8 +50,8 @@ class DefaultObjectFactory : public ObjectFactoryImpl {
       : className(core::className<T>()) {
   }
 
-  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<T>()) {
   }
 
diff --git 
a/core-framework/include/core/controller/ControllerServiceFactoryImpl.h 
b/core-framework/include/core/controller/ControllerServiceFactoryImpl.h
index a30bd48c6..32f1525a2 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<T>()) {
   }
 
-  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<T>()) {
   }
 
-  std::string getGroupName() const override {
-    return group_name_;
+  std::string getModuleName() const override {
+    return module_name_;
   }
 
   std::unique_ptr<ControllerServiceApi> 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 60766741c..b121f0197 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<Class>();
 
   std::vector<MinifiPropertyDefinition> class_properties = 
utils::toProperties(Class::Properties, string_vector_cache);
+  std::vector<MinifiStringView> 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<uint32_t>(class_properties.size()),
       .class_properties_ptr = class_properties.data(),
 
+      .provided_interfaces_count = 
gsl::narrow<uint32_t>(provided_interfaces.size()),
+      .provided_interfaces_ptr = provided_interfaces.data(),
+
       .callbacks = MinifiControllerServiceCallbacks{
           .create = [](MinifiControllerServiceMetadata metadata) -> 
MINIFI_OWNED void* {
             try {
@@ -181,6 +191,19 @@ void useControllerServiceClassDefinition(Fn&& fn) {
               static_cast<Class*>(self)->disable();
             } catch (...) {}
           },
+          .get_interface = [](void* self, MinifiStringView interface_name) -> 
void* {
+            try {
+              if constexpr (requires { Class::ProvidedInterfaces; }) {
+                const std::string_view name_view{interface_name.data, 
interface_name.length};
+                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/minifi-api/include/minifi-cpp/core/ControllerServiceType.h 
b/extensions/stable-api-testing/AnimalControllerServiceApis.h
similarity index 58%
copy from minifi-api/include/minifi-cpp/core/ControllerServiceType.h
copy to extensions/stable-api-testing/AnimalControllerServiceApis.h
index 6d5b39de3..49c45cee0 100644
--- a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h
+++ b/extensions/stable-api-testing/AnimalControllerServiceApis.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
@@ -14,22 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#pragma once
 
-#include <string>
-#include "minifi-cpp/core/ControllerServiceTypeDefinition.h"
+#pragma once
 
-namespace org::apache::nifi::minifi::core {
+#include <cinttypes>
 
-struct ControllerServiceType {
-  std::string artifact;
-  std::string group;
-  std::string type;
+namespace org::apache::nifi::minifi::api_testing {
+class NumberOfLegsControllerApi {
+ public:
+  virtual ~NumberOfLegsControllerApi() = default;
+  virtual uint8_t numberOfLegs() const = 0;
+};
 
-  ControllerServiceType(const ControllerServiceTypeDefinition& definition)  // 
NOLINT(runtime/explicit)
-    : artifact(definition.artifact),
-      group(definition.group),
-      type(definition.type) {}
+class CanFlyControllerApi {
+ public:
+  virtual ~CanFlyControllerApi() = default;
+  virtual bool canFly() const = 0;
 };
 
-}  // namespace org::apache::nifi::minifi::core
+}  // namespace org::apache::nifi::minifi::api_testing
diff --git a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h 
b/extensions/stable-api-testing/AnimalControllerServices.cpp
similarity index 56%
copy from minifi-api/include/minifi-cpp/core/ControllerServiceType.h
copy to extensions/stable-api-testing/AnimalControllerServices.cpp
index 6d5b39de3..f05ab6eea 100644
--- a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h
+++ b/extensions/stable-api-testing/AnimalControllerServices.cpp
@@ -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
@@ -14,22 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#pragma once
 
-#include <string>
-#include "minifi-cpp/core/ControllerServiceTypeDefinition.h"
+#include "AnimalControllerServices.h"
 
-namespace org::apache::nifi::minifi::core {
+#include "api/utils/ProcessorConfigUtils.h"
+namespace org::apache::nifi::minifi::api_testing {
 
-struct ControllerServiceType {
-  std::string artifact;
-  std::string group;
-  std::string type;
+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));
 
-  ControllerServiceType(const ControllerServiceTypeDefinition& definition)  // 
NOLINT(runtime/explicit)
-    : artifact(definition.artifact),
-      group(definition.group),
-      type(definition.type) {}
-};
+  return MINIFI_STATUS_SUCCESS;
+}
 
-}  // namespace org::apache::nifi::minifi::core
+}  // namespace org::apache::nifi::minifi::api_testing
diff --git a/extensions/stable-api-testing/AnimalControllerServices.h 
b/extensions/stable-api-testing/AnimalControllerServices.h
new file mode 100644
index 000000000..6a6e1e78e
--- /dev/null
+++ b/extensions/stable-api-testing/AnimalControllerServices.h
@@ -0,0 +1,69 @@
+/**
+ * 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_testing {
+/// DogController and DuckController are testing that one ControllerService 
type can implement multiple interfaces
+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<core::PropertyReference>({
+      HasJetpack,
+  });
+  EXTENSIONAPI static constexpr auto ProvidedInterfaces =
+      
std::to_array<core::ProvidedInterface>({core::createProvidedInterface<CanFlyControllerApi,
 DogController>(),
+          core::createProvidedInterface<NumberOfLegsControllerApi, 
DogController>()});
+
+  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<core::PropertyReference, 0> 
Properties = {};
+  EXTENSIONAPI static constexpr auto ProvidedInterfaces =
+      
std::to_array<core::ProvidedInterface>({core::createProvidedInterface<CanFlyControllerApi,
 DuckController>(),
+          core::createProvidedInterface<NumberOfLegsControllerApi, 
DuckController>()});
+  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_testing
diff --git a/extensions/stable-api-testing/CMakeLists.txt 
b/extensions/stable-api-testing/CMakeLists.txt
new file mode 100644
index 000000000..1f3a8e0de
--- /dev/null
+++ b/extensions/stable-api-testing/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-testing SHARED ${SOURCES})
+
+target_link_libraries(minifi-stable-api-testing minifi-cpp-extension-lib)
+
+register_c_api_extension(minifi-stable-api-testing "Stable API Sandbox" 
SANDBOX-EXTENSION "Stable API Sandbox" "extensions/stable-api-testing/tests")
diff --git a/extensions/stable-api-testing/ExtensionInitializer.cpp 
b/extensions/stable-api-testing/ExtensionInitializer.cpp
new file mode 100644
index 000000000..dae253407
--- /dev/null
+++ b/extensions/stable-api-testing/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<minifi::api_testing::ZooProcessor>(extension);
+  
minifi::api::core::registerControllerServices<minifi::api_testing::DogController,
 minifi::api_testing::DuckController>(extension);
+}
diff --git a/extensions/stable-api-testing/ZooProcessor.cpp 
b/extensions/stable-api-testing/ZooProcessor.cpp
new file mode 100644
index 000000000..79314c75b
--- /dev/null
+++ b/extensions/stable-api-testing/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_testing {
+
+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* const can_fly = 
reinterpret_cast<CanFlyControllerApi*>(*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* const num_legs = 
reinterpret_cast<NumberOfLegsControllerApi*>(*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_testing
diff --git a/extensions/stable-api-testing/ZooProcessor.h 
b/extensions/stable-api-testing/ZooProcessor.h
new file mode 100644
index 000000000..7c028b7d8
--- /dev/null
+++ b/extensions/stable-api-testing/ZooProcessor.h
@@ -0,0 +1,65 @@
+/**
+ * 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_testing {
+
+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<CanFlyControllerApi>()
+          .build();
+
+  EXTENSIONAPI static constexpr auto NumberOfLegsService =
+      core::PropertyDefinitionBuilder<>::createProperty("Number of legs 
service")
+          .withDescription("Test NumberOfLegsService")
+          .isRequired(true)
+          .withAllowedTypes<NumberOfLegsControllerApi>()
+          .build();
+
+  EXTENSIONAPI static constexpr auto Properties = 
std::to_array<core::PropertyReference>({
+      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_testing
diff --git a/extensions/stable-api-testing/tests/CMakeLists.txt 
b/extensions/stable-api-testing/tests/CMakeLists.txt
new file mode 100644
index 000000000..55614119c
--- /dev/null
+++ b/extensions/stable-api-testing/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-testing")
+    createTests(${testfilename})
+    target_link_libraries(${testfilename} Catch2WithMain)
+    target_link_libraries(${testfilename} minifi-stable-api-testing)
+    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-testing/tests/ZooTests.cpp 
b/extensions/stable-api-testing/tests/ZooTests.cpp
new file mode 100644
index 000000000..d1f8c1735
--- /dev/null
+++ b/extensions/stable-api-testing/tests/ZooTests.cpp
@@ -0,0 +1,69 @@
+/**
+ *
+ * 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_testing::test {
+TEST_CASE("ZooTest") {
+  minifi::test::SingleProcessorTestController 
controller(minifi::test::utils::make_custom_c_processor<ZooProcessor>(
+      core::ProcessorMetadata{utils::Identifier{}, "ZooProcessor", 
logging::LoggerFactory<ZooProcessor>::getLogger()}));
+  const auto dog_with_jetpack = 
minifi::test::utils::make_custom_c_controller_service<DogController>(
+      core::ControllerServiceMetadata{utils::Identifier{}, "DogController", 
logging::LoggerFactory<DogController>::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<DuckController>(core::ControllerServiceMetadata{utils::Identifier{},
+      "DuckController",
+      logging::LoggerFactory<DuckController>::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_testing::ZooProcessor] 
[critical] Can dog_with_jetpack fly? true"));
+    
CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_testing::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_testing::ZooProcessor]
 [critical] Can duck fly? true"));
+    
CHECK(LogTestController::getInstance().contains("[org::apache::nifi::minifi::api_testing::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_testing::ZooProcessor]
 [critical] Can duck fly? true"));
+  }
+}
+
+}  // namespace org::apache::nifi::minifi::api_testing::test
diff --git a/extensions/standard-processors/controllers/XMLReader.cpp 
b/extensions/standard-processors/controllers/XMLReader.cpp
index 980ccc721..60f8a7cbb 100644
--- a/extensions/standard-processors/controllers/XMLReader.cpp
+++ b/extensions/standard-processors/controllers/XMLReader.cpp
@@ -21,8 +21,9 @@
 #include <ranges>
 
 #include "core/Resource.h"
-#include "utils/TimeUtil.h"
+#include "minifi-cpp/Exception.h"
 #include "minifi-cpp/utils/gsl.h"
+#include "utils/TimeUtil.h"
 
 namespace org::apache::nifi::minifi::standard {
 
diff --git a/libminifi/include/core/state/nodes/AgentInformation.h 
b/libminifi/include/core/state/nodes/AgentInformation.h
index d76846b8e..89b238202 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<SerializedResponseNode> serializeComponentManifest(const 
Components& components);
 
 class Bundles : public DeviceInformation {
diff --git a/libminifi/src/core/ClassLoader.cpp 
b/libminifi/src/core/ClassLoader.cpp
index 463b2a2e4..ee97b3af9 100644
--- a/libminifi/src/core/ClassLoader.cpp
+++ b/libminifi/src/core/ClassLoader.cpp
@@ -18,14 +18,13 @@
 #include "core/ClassLoader.h"
 
 #include <memory>
+#include <ranges>
 #include <string>
 
-#include "core/logging/LoggerFactory.h"
-#include "range/v3/action/sort.hpp"
-#include "range/v3/action/unique.hpp"
+#include "core/ObjectFactory.h"
 #include "core/Processor.h"
 #include "core/controller/ControllerService.h"
-#include "core/ObjectFactory.h"
+#include "core/logging/LoggerFactory.h"
 
 namespace org::apache::nifi::minifi::core {
 
@@ -48,7 +47,7 @@ class ClassLoaderImpl : public ClassLoader {
 
   void unregisterClass(const std::string& clazz) override;
 
-  [[nodiscard]] std::optional<std::string> getGroupForClass(const std::string 
&class_name) const override;
+  [[nodiscard]] std::optional<std::string> getModuleForClass(std::string_view 
class_name) const override;
 
   [[nodiscard]] std::unique_ptr<CoreComponent> instantiate(const std::string 
&class_name, const std::string &name, std::function<bool(CoreComponent*)> 
filter) override;
 
@@ -61,7 +60,7 @@ class ClassLoaderImpl : public ClassLoader {
   ~ClassLoaderImpl() override = default;
 
  private:
-  std::map<std::string, std::unique_ptr<ObjectFactory>> loaded_factories_;
+  std::map<std::string, std::unique_ptr<ObjectFactory>, std::less<>> 
loaded_factories_;
   std::map<std::string, ClassLoaderImpl> class_loaders_;
   mutable std::mutex internal_mutex_;
   std::shared_ptr<logging::Logger> logger_;
@@ -78,7 +77,7 @@ ClassLoader &ClassLoader::getDefaultClassLoader() {
 }
 
 ClassLoader& ClassLoaderImpl::getClassLoader(const std::string& child_name) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   auto it = class_loaders_.find(child_name);
   if (it != class_loaders_.end()) {
     return it->second;
@@ -95,7 +94,7 @@ ClassLoader& ClassLoaderImpl::getClassLoader(const 
std::string& child_name) {
 }
 
 void ClassLoaderImpl::registerClass(const std::string &clazz, 
std::unique_ptr<ObjectFactory> factory) {
-    std::lock_guard<std::mutex> lock(internal_mutex_);
+    std::scoped_lock lock(internal_mutex_);
     if (loaded_factories_.contains(clazz)) {
       logger_->log_error("Class '{}' is already registered at '{}'", clazz, 
name_);
       return;
@@ -125,10 +124,10 @@ class ProcessorFactoryWrapper : public ObjectFactoryImpl {
 
   [[nodiscard]] gsl::owner<CoreComponent*> createRaw(const std::string &name, 
const utils::Identifier &uuid) override {
     auto logger = logging::LoggerFactoryBase::getAliasedLogger(getClassName(), 
uuid);
-    return new Processor(name, uuid, factory_->create({.uuid = uuid, .name = 
name, .logger = std::move(logger)}));
+    return gsl::owner<CoreComponent*>{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 +142,7 @@ class ProcessorFactoryWrapper : public ObjectFactoryImpl {
 class ControllerServiceFactoryWrapper : public ObjectFactoryImpl {
  public:
   explicit 
ControllerServiceFactoryWrapper(std::unique_ptr<controller::ControllerServiceFactory>
 factory)
-    : ObjectFactoryImpl(factory->getGroupName()),
+    : ObjectFactoryImpl(factory->getModuleName()),
       factory_(std::move(factory)) {}
 
   [[nodiscard]] std::unique_ptr<CoreComponent> create(const std::string &name) 
override {
@@ -160,11 +159,11 @@ class ControllerServiceFactoryWrapper : public 
ObjectFactoryImpl {
 
   [[nodiscard]] gsl::owner<CoreComponent*> createRaw(const std::string &name, 
const utils::Identifier &uuid) override {
     auto logger = logging::LoggerFactoryBase::getAliasedLogger(getClassName(), 
uuid);
-    return new controller::ControllerService(name, uuid, 
factory_->create({.uuid = uuid, .name = name, .logger = std::move(logger)}));
+    return gsl::owner<CoreComponent*>{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 {
@@ -186,7 +185,7 @@ void ClassLoaderImpl::registerClass(const std::string 
&clazz, std::unique_ptr<co
 }
 
 void ClassLoaderImpl::unregisterClass(const std::string& clazz) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   if (loaded_factories_.erase(clazz) == 0) {
     logger_->log_error("Could not unregister non-registered class '{}' at 
'{}'", clazz, name_);
   } else {
@@ -194,26 +193,25 @@ void ClassLoaderImpl::unregisterClass(const std::string& 
clazz) {
   }
 }
 
-std::optional<std::string> ClassLoaderImpl::getGroupForClass(const std::string 
&class_name) const {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
-  for (const auto& child_loader : class_loaders_) {
-    std::optional<std::string> group = 
child_loader.second.getGroupForClass(class_name);
-    if (group) {
+std::optional<std::string> ClassLoaderImpl::getModuleForClass(const 
std::string_view class_name) const {
+  std::scoped_lock lock(internal_mutex_);
+  for (const auto &val : class_loaders_ | std::views::values) {
+    if (std::optional<std::string> group = val.getModuleForClass(class_name)) {
       return group;
     }
   }
   auto factory = loaded_factories_.find(class_name);
   if (factory != loaded_factories_.end()) {
-    return factory->second->getGroupName();
+    return factory->second->getModuleName();
   }
   return {};
 }
 
 std::unique_ptr<CoreComponent> ClassLoaderImpl::instantiate(const std::string 
&class_name, const std::string &name, std::function<bool(CoreComponent*)> 
filter) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   // allow subsequent classes to override functionality
-  for (auto& child_loader : class_loaders_) {
-    if (auto result = child_loader.second.instantiate(class_name, name, 
filter)) {
+  for (auto &val : class_loaders_ | std::views::values) {
+    if (auto result = val.instantiate(class_name, name, filter)) {
       return result;
     }
   }
@@ -228,10 +226,10 @@ std::unique_ptr<CoreComponent> 
ClassLoaderImpl::instantiate(const std::string &c
 }
 
 std::unique_ptr<CoreComponent> ClassLoaderImpl::instantiate(const std::string 
&class_name, const utils::Identifier &uuid, std::function<bool(CoreComponent*)> 
filter) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   // allow subsequent classes to override functionality
-  for (auto& child_loader : class_loaders_) {
-    if (auto result = child_loader.second.instantiate(class_name, uuid, 
filter)) {
+  for (auto &val : class_loaders_ | std::views::values) {
+    if (auto result = val.instantiate(class_name, uuid, filter)) {
       return result;
     }
   }
@@ -246,10 +244,10 @@ std::unique_ptr<CoreComponent> 
ClassLoaderImpl::instantiate(const std::string &c
 }
 
 std::unique_ptr<CoreComponent> ClassLoaderImpl::instantiate(const std::string 
&class_name, const std::string &name, const utils::Identifier &uuid, 
std::function<bool(CoreComponent*)> filter) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   // allow subsequent classes to override functionality
-  for (auto& child_loader : class_loaders_) {
-    if (auto result = child_loader.second.instantiate(class_name, name, uuid, 
filter)) {
+  for (auto &val : class_loaders_ | std::views::values) {
+    if (auto result = val.instantiate(class_name, name, uuid, filter)) {
       return result;
     }
   }
@@ -264,10 +262,10 @@ std::unique_ptr<CoreComponent> 
ClassLoaderImpl::instantiate(const std::string &c
 }
 
 gsl::owner<CoreComponent*> ClassLoaderImpl::instantiateRaw(const std::string 
&class_name, const std::string &name, std::function<bool(CoreComponent*)> 
filter) {
-  std::lock_guard<std::mutex> lock(internal_mutex_);
+  std::scoped_lock lock(internal_mutex_);
   // allow subsequent classes to override functionality
-  for (auto& child_loader : class_loaders_) {
-    if (gsl::owner<CoreComponent*> result = 
child_loader.second.instantiateRaw(class_name, name, filter)) {
+  for (auto &val : class_loaders_ | std::views::values) {
+    if (gsl::owner<CoreComponent*> result = val.instantiateRaw(class_name, 
name, filter)) {
       return result;
     }
   }
@@ -277,7 +275,7 @@ gsl::owner<CoreComponent*> 
ClassLoaderImpl::instantiateRaw(const std::string &cl
     if (filter(obj)) {
       return obj;
     }
-    delete obj;
+    delete obj;  // NOLINT(cppcoreguidelines-owning-memory)
   }
   return nullptr;
 }
diff --git a/libminifi/src/core/state/nodes/AgentInformation.cpp 
b/libminifi/src/core/state/nodes/AgentInformation.cpp
index a3165c588..1c73ee2a4 100644
--- a/libminifi/src/core/state/nodes/AgentInformation.cpp
+++ b/libminifi/src/core/state/nodes/AgentInformation.cpp
@@ -17,12 +17,18 @@
  */
 #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/AttributeProviderService.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 +36,71 @@ 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
+      || allowed_type == 
controllers::AttributeProviderService::ProvidesApi.type;
+}
+
+std::string allowedTypeArtifactName(const std::string_view allowed_type, const 
std::string_view prop_owner_type) {
+  if (minifiSystemAllowedType(allowed_type)) {
+    return "minifi-system";
+  }
+
+  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";
+}
+
 void serializeClassDescription(const std::vector<ClassDescription>& 
descriptions, const std::string& name, SerializedResponseNode& response) {
   if (descriptions.empty()) {
     return;
   }
   SerializedResponseNode type{.name = name, .array = true};
   std::vector<SerializedResponseNode> 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 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()});
@@ -99,12 +143,12 @@ void serializeClassDescription(const 
std::vector<ClassDescription>& 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()});
@@ -114,13 +158,13 @@ void serializeClassDescription(const 
std::vector<ClassDescription>& 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)});
@@ -154,12 +198,12 @@ std::vector<SerializedResponseNode> 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 220ec9513..68617d522 100644
--- a/libminifi/src/minifi-c.cpp
+++ b/libminifi/src/minifi-c.cpp
@@ -155,8 +155,8 @@ class CProcessorFactory : public 
minifi::core::ProcessorFactory {
 
 class CControllerServiceFactory : public 
minifi::core::controller::ControllerServiceFactory {
  public:
-  CControllerServiceFactory(std::string group_name, std::string class_name, 
minifi::utils::CControllerServiceClassDescription class_description)
-      : group_name_(std::move(group_name)),
+  CControllerServiceFactory(std::string module_name, std::string class_name, 
minifi::utils::CControllerServiceClassDescription class_description)
+      : module_name_(std::move(module_name)),
         class_name_(std::move(class_name)),
         class_description_(std::move(class_description)) {}
 
@@ -164,7 +164,7 @@ class CControllerServiceFactory : public 
minifi::core::controller::ControllerSer
     return 
std::make_unique<minifi::utils::CControllerService>(class_description_, 
std::move(metadata));
   }
 
-  [[nodiscard]] std::string getGroupName() const override { return 
group_name_; }
+  [[nodiscard]] std::string getModuleName() const override { return 
module_name_; }
 
   [[nodiscard]] std::string getClassName() const override { return 
class_name_; }
 
@@ -176,7 +176,7 @@ class CControllerServiceFactory : public 
minifi::core::controller::ControllerSer
   ~CControllerServiceFactory() override = default;
 
  private:
-  std::string group_name_;
+  std::string module_name_;
   std::string class_name_;
   minifi::utils::CControllerServiceClassDescription class_description_;
 };
@@ -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<core::ControllerServiceType> 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<MinifiControllerService*>(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<MinifiControllerService*>(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 7bc08750f..bfaaa3fa1 100644
--- a/libminifi/test/unit/ComponentManifestTests.cpp
+++ b/libminifi/test/unit/ComponentManifestTests.cpp
@@ -27,6 +27,7 @@
 #include "core/Resource.h"
 #include "core/state/nodes/AgentInformation.h"
 #include "core/ProcessorImpl.h"
+#include "range/v3/algorithm/find_if.hpp"
 
 using minifi::state::response::SerializedResponseNode;
 
@@ -38,6 +39,25 @@ SerializedResponseNode& get(SerializedResponseNode& node, 
const std::string& fie
   throw std::logic_error("No field '" + field + "'");
 }
 
+namespace org::apache::nifi::minifi::test::apple {
+class ExampleApacheService : public core::controller::ControllerServiceBase, 
public core::controller::ControllerServiceHandle {
+ public:
+  using ControllerServiceBase::ControllerServiceBase;
+
+  void initialize() override {}
+  void onEnable() override {}
+
+  [[nodiscard]] ControllerServiceHandle* getControllerServiceHandle() override 
{ return this; }
+
+  static constexpr const char* Description = "An example service";
+  static constexpr auto Properties = std::array<core::PropertyReference, 0>{};
+  static constexpr bool SupportsDynamicProperties = false;
+  ADD_COMMON_VIRTUAL_FUNCTIONS_FOR_CONTROLLER_SERVICES
+};
+
+REGISTER_RESOURCE(ExampleApacheService, ControllerService);
+}  // namespace org::apache::nifi::minifi::test::apple
+
 namespace test::apple {
 
 class ExampleService : public core::controller::ControllerServiceBase, public 
core::controller::ControllerServiceHandle {
@@ -67,7 +87,12 @@ class ExampleProcessor : public core::ProcessorImpl {
       .isRequired(false)
       .withAllowedTypes<ExampleService>()
       .build();
-  static constexpr auto Properties = 
std::to_array<core::PropertyReference>({ExampleProperty});
+  static constexpr auto ExampleApacheProperty = 
core::PropertyDefinitionBuilder<>::createProperty("Example Apache Property")
+      .withDescription("An example property")
+      .isRequired(false)
+      
.withAllowedTypes<org::apache::nifi::minifi::test::apple::ExampleApacheService>()
+      .build();
+  static constexpr auto Properties = 
std::to_array<core::PropertyReference>({ExampleProperty, 
ExampleApacheProperty});
   static constexpr auto Relationships = 
std::array<core::RelationshipDefinition, 0>{};
   static constexpr bool SupportsDynamicProperties = false;
   static constexpr bool SupportsDynamicRelationships = false;
@@ -93,27 +118,38 @@ TEST_CASE("Manifest indicates property type requirement") {
 
   auto& processors = get(nodes.at(0), "processors").children;
 
-  auto example_proc_it = std::find_if(processors.begin(), processors.end(), 
[&] (auto& proc) {
+  auto example_proc_it = std::find_if(processors.begin(), processors.end(), 
[&](auto& proc) {
     return get(proc, "type").value == "test.apple.ExampleProcessor";
   });
   REQUIRE(example_proc_it != processors.end());
 
   auto& properties = get(*example_proc_it, "propertyDescriptors").children;
 
-  auto prop_it = std::find_if(properties.begin(), properties.end(), [&] (auto& 
prop) {
-    return get(prop, "name").value == "Example Property";
-  });
+  {
+    auto prop_it = std::find_if(properties.begin(), properties.end(), 
[&](auto& prop) { return get(prop, "name").value == "Example Property"; });
+
+    REQUIRE(prop_it != properties.end());
 
-  REQUIRE(prop_it != properties.end());
+    // TODO(adebreceni): based on Property::types_ a property could accept
+    //    multiple types, these would be dumped into the same object as the 
type of
+    //    field "typeProvidedByValue" is not an array but an object
+    auto& type = get(*prop_it, "typeProvidedByValue");
 
-  // TODO(adebreceni): based on Property::types_ a property could accept
-  //    multiple types, these would be dumped into the same object as the type 
of
-  //    field "typeProvidedByValue" is not an array but an object
-  auto& type = get(*prop_it, "typeProvidedByValue");
+    CHECK(get(type, "type").value == "test.apple.ExampleService");
+    CHECK(get(type, "group").value == "test.apple");
+    CHECK(get(type, "artifact").value == "minifi-system");
+  }
+  {
+    auto prop_it = ranges::find_if(properties, [&](auto& prop) { return 
get(prop, "name").value == "Example Apache Property"; });
+    REQUIRE(prop_it != properties.end());
 
-  REQUIRE(get(type, "type").value == "test.apple.ExampleService");
-  REQUIRE(get(type, "group").value == GROUP_STR);  // fix string
-  REQUIRE(get(type, "artifact").value == "minifi-system");
+    auto& type = get(*prop_it, "typeProvidedByValue");
+
+    CHECK(get(type, "type").value == 
"org.apache.nifi.minifi.test.apple.ExampleApacheService");
+    // Backward compatibility test, if the group starts with 
org.apache.nifi.minifi we use that
+    CHECK(get(type, "group").value == "org.apache.nifi.minifi");
+    CHECK(get(type, "artifact").value == "minifi-system");
+  }
 }
 
 TEST_CASE("Processors do not get instantiated during manifest creation") {
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 000000000..3b2af8cf9
--- /dev/null
+++ 
b/minifi-api/common/include/minifi-cpp/core/ProvidedControllerServiceInterface.h
@@ -0,0 +1,48 @@
+/**
+ * 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 <string_view>
+#include <type_traits>
+
+#include "core/ClassName.h"
+
+namespace org::apache::nifi::minifi::core {
+
+struct ProvidedInterface {
+  /// fully-qualified class name of the implemented ControllerService interface
+  std::string_view name;
+  /// casts a pointer of the ControllerService from its dynamic type to a 
pointer of an implemented interface type. Needed to support cases where they
+  /// are not the same, like multiple inheritance, virtual inheritance, or 
contained non-first member implementing the interface
+  void* (*cast)(void* instance);
+};
+
+/// casts a pointer of the ControllerService from its dynamic type to a 
pointer of an implemented interface type. Needed to support cases where they
+/// are not the same, like multiple inheritance, virtual inheritance, or 
contained non-first member implementing the interface
+template<typename Interface, typename ConcreteService>
+void* castProvidedControllerServiceToInterface(void* instance) {
+  auto* concrete = static_cast<ConcreteService*>(instance);
+  auto* interface_ptr = static_cast<Interface*>(concrete);
+  return interface_ptr;
+}
+
+template<typename Interface, typename ConcreteService>
+constexpr ProvidedInterface createProvidedInterface() {
+  return ProvidedInterface{className<Interface>(), 
&castProvidedControllerServiceToInterface<Interface, ConcreteService>};
+}
+
+}  // 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 8294dd91f..1fdfe953c 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 c05d20396..e9337d5e7 100644
--- a/minifi-api/include/minifi-cpp/controllers/RecordSetReader.h
+++ b/minifi-api/include/minifi-cpp/controllers/RecordSetReader.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
@@ -16,13 +16,12 @@
  */
 #pragma once
 
-#include "minifi-cpp/core/controller/ControllerServiceHandle.h"
+#include <expected>
+
 #include "minifi-cpp/core/ControllerServiceTypeDefinition.h"
 #include "minifi-cpp/core/Record.h"
-#include "utils/Enum.h"
-#include "utils/ProcessorConfigUtils.h"
+#include "minifi-cpp/core/controller/ControllerServiceHandle.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 559ef0048..c377bca13 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<std::string> getGroupForClass(const 
std::string &class_name) const = 0;
+  [[nodiscard]] virtual std::optional<std::string> 
getModuleForClass(std::string_view class_name) const = 0;
 
   [[nodiscard]] virtual std::unique_ptr<CoreComponent> instantiate(const 
std::string &class_name, const std::string &name, 
std::function<bool(CoreComponent*)> filter) = 0;
 
diff --git a/minifi-api/include/minifi-cpp/core/ControllerServiceType.h 
b/minifi-api/include/minifi-cpp/core/ControllerServiceType.h
index 6d5b39de3..f45a8e3fb 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 <string>
+
 #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 8058151ee..8ff5039a9 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<CoreComponent*> createRaw(const std::string& /*name*/) = 
0;
   virtual std::unique_ptr<CoreComponent> create(const std::string& /*name*/, 
const utils::Identifier& /*uuid*/) = 0;
   virtual gsl::owner<CoreComponent*> 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 aafc571dc..17b8637eb 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<ControllerServiceApi> 
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;

Reply via email to