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

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

commit 4f35ccde0bb7f334f31575d2578c1c45b6d855df
Author: Gabor Gyimesi <[email protected]>
AuthorDate: Tue Feb 18 13:29:49 2025 +0100

    MINIFICPP-2376 Add support for parameter context inheritance
    
    Closes #1888
    
    Signed-off-by: Marton Szasz <[email protected]>
---
 CONFIGURE.md                                       |  28 +-
 .../tests/unit/FlowJsonTests.cpp                   | 331 ++++++++++++++++++++-
 .../tests/unit/YamlConfigurationTests.cpp          | 264 ++++++++++++++++
 libminifi/include/core/ParameterContext.h          |  10 +
 libminifi/include/core/flow/FlowSchema.h           |   1 +
 .../include/core/flow/StructuredConfiguration.h    |   1 +
 libminifi/src/core/ParameterContext.cpp            |   5 +
 libminifi/src/core/flow/FlowSchema.cpp             |   6 +-
 .../src/core/flow/StructuredConfiguration.cpp      |  56 ++++
 9 files changed, 698 insertions(+), 4 deletions(-)

diff --git a/CONFIGURE.md b/CONFIGURE.md
index 51e6219ae..1464320c1 100644
--- a/CONFIGURE.md
+++ b/CONFIGURE.md
@@ -139,11 +139,26 @@ Processor properties in flow configurations can be 
parameterized using parameter
  - `#` character can be used to escape the parameter syntax. E.g. if the 
`parameterName` parameter's value is `xxx` then `#{parameterName}` will be 
replaced with `xxx`, `##{parameterName}` will be replaced with 
`#{parameterName}`, and `#####{parameterName}` will be replaced with `##xxx`.
  - Sensitive parameters can only be assigned to sensitive properties and 
non-sensitive parameters can only be assigned to non-sensitive properties.
 
+Parameter contexts can be inherited from multiple other parameter contexts. 
The order of the parameter inheritance matters in the way that if a parameter 
is present in multiple parameter contexts, the parameter value assigned to the 
first parameter in the inheritance order will be used. No circular inheritance 
is allowed.
+
 An example for using parameters in a JSON configuration file:
 
 ```json
 {
     "parameterContexts": [
+        {
+            "identifier": "235e6b47-ea22-45cd-a472-545801db98e6",
+            "name": "common-parameter-context",
+            "description": "Common parameter context",
+            "parameters": [
+                {
+                    "name": "common_timeout",
+                    "description": "Common timeout seconds",
+                    "sensitive": false,
+                    "value": "30"
+                }
+            ],
+        },
         {
             "identifier": "804e6b47-ea22-45cd-a472-545801db98e6",
             "name": "root-process-group-context",
@@ -155,7 +170,8 @@ An example for using parameters in a JSON configuration 
file:
                     "sensitive": false,
                     "value": "/tmp/tail/file/path"
                 }
-            ]
+            ],
+            "inheritedParameterContexts": ["common-parameter-context"]
         }
     ],
     "rootGroup": {
@@ -194,6 +210,14 @@ An example for using parameters in a YAML configuration 
file:
     Flow Controller:
       name: MiNiFi Flow
     Parameter Contexts:
+      - id: 235e6b47-ea22-45cd-a472-545801db98e6
+        name: common-parameter-context
+        description: Common parameter context
+        Parameters:
+        - name: common_timeout
+          description: 'Common timeout seconds'
+          sensitive: false
+          value: 30
       - id: 804e6b47-ea22-45cd-a472-545801db98e6
         name: root-process-group-context
         description: Root process group parameter context
@@ -202,6 +226,8 @@ An example for using parameters in a YAML configuration 
file:
           description: 'Base dir of tailed files'
           sensitive: false
           value: /tmp/tail/file/path
+        Inherited Parameter Contexts:
+        - common-parameter-context
     Processors:
     - name: Tail test_file1.log
       id: 83b58f9f-e661-4634-96fb-0e82b92becdf
diff --git a/extensions/standard-processors/tests/unit/FlowJsonTests.cpp 
b/extensions/standard-processors/tests/unit/FlowJsonTests.cpp
index 14084be80..06bb859ca 100644
--- a/extensions/standard-processors/tests/unit/FlowJsonTests.cpp
+++ b/extensions/standard-processors/tests/unit/FlowJsonTests.cpp
@@ -29,6 +29,7 @@
 #include "Funnel.h"
 #include "core/Resource.h"
 #include "utils/crypto/property_encryption/PropertyEncryptionUtils.h"
+#include "unit/TestUtils.h"
 
 using namespace std::literals::chrono_literals;
 
@@ -1029,7 +1030,6 @@ TEST_CASE("NiFi flow json can use alternative targetUris 
field") {
   REQUIRE(port->getProperty("Port UUID") == 
"00000000-0000-0000-0000-000000000005");
 }
 
-
 TEST_CASE("Test parameters in controller services") {
   ConfigurationTestController test_controller;
   auto context = test_controller.getContext();
@@ -1149,4 +1149,333 @@ TEST_CASE("Parameters can be used in controller 
services in nested process group
   CHECK(impl->getProperty("Private Key").value() == 
"/opt/secrets/private-key.pem");
 }
 
+TEST_CASE("Test parameter context inheritance") {
+  ConfigurationTestController test_controller;
+  auto context = test_controller.getContext();
+  auto encrypted_parameter_value = 
minifi::utils::crypto::property_encryption::encrypt("value1", 
*context.sensitive_values_encryptor);
+  auto encrypted_sensitive_property_value = 
minifi::utils::crypto::property_encryption::encrypt("#{my_new_parameter}", 
*context.sensitive_values_encryptor);
+  core::flow::AdaptiveConfiguration config(context);
+
+  static const std::string CONFIG_JSON =
+      fmt::format(R"(
+{{
+  "parameterContexts": [
+    {{
+      "identifier": "721e10b7-8e00-3188-9a27-476cca376978",
+      "name": "inherited-context",
+      "description": "inherited parameter context",
+      "parameters": [
+        {{
+          "name": "my_new_parameter",
+          "description": "",
+          "sensitive": true,
+          "value": "{}"
+        }}
+      ],
+      "inheritedParameterContexts": ["base-context"]
+    }},
+    {{
+      "identifier": "521e10b7-8e00-3188-9a27-476cca376351",
+      "name": "base-context",
+      "description": "my base parameter context",
+      "parameters": [
+        {{
+          "name": "my_old_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "old_value"
+        }}
+      ]
+    }}
+  ],
+  "rootGroup": {{
+    "name": "MiNiFi Flow",
+    "processors": [{{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {{
+        "Sensitive Property": "{}",
+        "Simple Property": "#{{my_old_parameter}}"
+      }}
+    }}],
+    "parameterContextName": "inherited-context"
+  }}
+}})", encrypted_parameter_value, encrypted_sensitive_property_value);
+
+  std::unique_ptr<core::ProcessGroup> flow = 
config.getRootFromPayload(CONFIG_JSON);
+  REQUIRE(flow);
+
+  auto* proc = flow->findProcessorByName("MyProcessor");
+  REQUIRE(proc);
+  REQUIRE(proc->getProperty("Sensitive Property") == "value1");
+  REQUIRE(proc->getProperty("Simple Property") == "old_value");
+}
+
+TEST_CASE("Parameter context can not inherit from a itself") {
+  ConfigurationTestController test_controller;
+
+  core::flow::AdaptiveConfiguration config(test_controller.getContext());
+
+  static const std::string CONFIG_JSON =
+      R"(
+{
+  "parameterContexts": [
+    {
+      "identifier": "521e10b7-8e00-3188-9a27-476cca376351",
+      "name": "base-context",
+      "description": "my base parameter context",
+      "parameters": [
+        {
+          "name": "my_old_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "old_value"
+        }
+      ],
+      "inheritedParameterContexts": ["base-context"]
+    }
+  ],
+  "rootGroup": {
+    "name": "MiNiFi Flow",
+    "processors": [{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {
+        "Simple Property": "#{my_old_parameter}"
+      }
+    }],
+    "parameterContextName": "base-context"
+  }
+})";
+
+  REQUIRE_THROWS_WITH(config.getRootFromPayload(CONFIG_JSON), "Inherited 
parameter context 'base-context' cannot be the same as the parameter context!");
+}
+
+TEST_CASE("Parameter context can not inherit from non-existing parameter 
context") {
+  ConfigurationTestController test_controller;
+
+  core::flow::AdaptiveConfiguration config(test_controller.getContext());
+
+  static const std::string CONFIG_JSON =
+      R"(
+{
+  "parameterContexts": [
+    {
+      "identifier": "521e10b7-8e00-3188-9a27-476cca376351",
+      "name": "base-context",
+      "description": "my base parameter context",
+      "parameters": [
+        {
+          "name": "my_old_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "old_value"
+        }
+      ],
+      "inheritedParameterContexts": ["unknown"]
+    }
+  ],
+  "rootGroup": {
+    "name": "MiNiFi Flow",
+    "processors": [{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {
+        "Simple Property": "#{my_old_parameter}"
+      }
+    }],
+    "parameterContextName": "base-context"
+  }
+})";
+
+  REQUIRE_THROWS_WITH(config.getRootFromPayload(CONFIG_JSON), "Inherited 
parameter context 'unknown' does not exist!");
+}
+
+TEST_CASE("Cycles are not allowed in parameter context inheritance") {
+  ConfigurationTestController test_controller;
+
+  core::flow::AdaptiveConfiguration config(test_controller.getContext());
+
+  static const std::string CONFIG_JSON =
+      R"(
+{
+  "parameterContexts": [
+    {
+      "identifier": "123e10b7-8e00-3188-9a27-476cca376351",
+      "name": "a-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "a_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "a_value"
+        }
+      ],
+      "inheritedParameterContexts": ["c-context"]
+    },
+    {
+      "identifier": "456e10b7-8e00-3188-9a27-476cca376351",
+      "name": "b-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "b_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "b_value"
+        }
+      ],
+      "inheritedParameterContexts": ["a-context"]
+    },
+    {
+      "identifier": "789e10b7-8e00-3188-9a27-476cca376351",
+      "name": "c-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "c_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "c_value"
+        }
+      ],
+      "inheritedParameterContexts": ["d-context", "b-context"]
+    },
+    {
+      "identifier": "101e10b7-8e00-3188-9a27-476cca376351",
+      "name": "d-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "d_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "d_value"
+        }
+      ],
+      "inheritedParameterContexts": []
+    }
+  ],
+  "rootGroup": {
+    "name": "MiNiFi Flow",
+    "processors": [{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {
+        "Simple Property": "#{my_old_parameter}"
+      }
+    }],
+    "parameterContextName": "c-context"
+  }
+})";
+
+  REQUIRE_THROWS_AS(config.getRootFromPayload(CONFIG_JSON), 
std::invalid_argument);
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Circular 
references in Parameter Context inheritance are not allowed. Inheritance cycle 
was detected in parameter context"));
+}
+
+TEST_CASE("Parameter context inheritance order is respected") {
+  ConfigurationTestController test_controller;
+  core::flow::AdaptiveConfiguration config(test_controller.getContext());
+
+  static const std::string CONFIG_JSON =
+      R"(
+{
+  "parameterContexts": [
+    {
+      "identifier": "721e10b7-8e00-3188-9a27-476cca376978",
+      "name": "a-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "a_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "1"
+        },
+        {
+          "name": "b_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "2"
+        }
+      ]
+    },
+    {
+      "identifier": "521e10b7-8e00-3188-9a27-476cca376351",
+      "name": "b-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "b_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "3"
+        },
+        {
+          "name": "c_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "4"
+        }
+      ]
+    },
+    {
+      "identifier": "123e10b7-8e00-3188-9a27-476cca376351",
+      "name": "c-context",
+      "description": "",
+      "parameters": [
+        {
+          "name": "c_parameter",
+          "description": "",
+          "sensitive": false,
+          "value": "5"
+        }
+      ],
+      "inheritedParameterContexts": ["b-context", "a-context"]
+    }
+  ],
+  "rootGroup": {
+    "name": "MiNiFi Flow",
+    "processors": [{
+      "identifier": "00000000-0000-0000-0000-000000000001",
+      "name": "MyProcessor",
+      "type": "org.apache.nifi.processors.DummyFlowJsonProcessor",
+      "schedulingStrategy": "TIMER_DRIVEN",
+      "schedulingPeriod": "3 sec",
+      "properties": {
+        "My A Property": "#{a_parameter}",
+        "My B Property": "#{b_parameter}",
+        "My C Property": "#{c_parameter}"
+      }
+    }],
+    "parameterContextName": "c-context"
+  }
+})";
+
+  std::unique_ptr<core::ProcessGroup> flow = 
config.getRootFromPayload(CONFIG_JSON);
+  REQUIRE(flow);
+
+  auto* proc = flow->findProcessorByName("MyProcessor");
+  std::string value;
+  REQUIRE(proc->getDynamicProperty("My A Property", value));
+  CHECK(value == "1");
+  REQUIRE(proc->getDynamicProperty("My B Property", value));
+  CHECK(value == "3");
+  REQUIRE(proc->getDynamicProperty("My C Property", value));
+  CHECK(value == "5");
+}
+
 }  // namespace org::apache::nifi::minifi::test
diff --git 
a/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp 
b/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp
index 876167a4d..44c27ca00 100644
--- a/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp
+++ b/extensions/standard-processors/tests/unit/YamlConfigurationTests.cpp
@@ -1876,4 +1876,268 @@ Process Groups:
   CHECK(impl->getProperty("Private Key").value() == 
"/opt/secrets/private-key.pem");
 }
 
+TEST_CASE("Test parameter context inheritance", "[YamlConfiguration]") {
+  ConfigurationTestController test_controller;
+  auto context = test_controller.getContext();
+  auto encrypted_parameter_value = 
minifi::utils::crypto::property_encryption::encrypt("value1", 
*context.sensitive_values_encryptor);
+  auto encrypted_sensitive_property_value = 
minifi::utils::crypto::property_encryption::encrypt("#{my_new_parameter}", 
*context.sensitive_values_encryptor);
+  core::YamlConfiguration yaml_config(context);
+
+  static const std::string TEST_CONFIG_YAML =
+      fmt::format(R"(
+MiNiFi Config Version: 3
+Flow Controller:
+  name: flowconfig
+Parameter Contexts:
+  - id: 721e10b7-8e00-3188-9a27-476cca376978
+    name: inherited-context
+    description: my parameter context
+    Parameters:
+    - name: my_new_parameter
+      description: inherited parameter context
+      sensitive: true
+      value: {}
+    Inherited Parameter Contexts: [base-context]
+  - id: 521e10b7-8e00-3188-9a27-476cca376351
+    name: base-context
+    description: my base parameter context
+    Parameters:
+    - name: my_old_parameter
+      description: ''
+      sensitive: false
+      value: old_value
+Processors:
+- id: b0c04f28-0158-1000-0000-000000000000
+  name: DummyFlowYamlProcessor
+  class: org.apache.nifi.processors.DummyFlowYamlProcessor
+  max concurrent tasks: 1
+  scheduling strategy: TIMER_DRIVEN
+  scheduling period: 1 sec
+  auto-terminated relationships list: [success]
+  Properties:
+    Simple Property: "#{{my_old_parameter}}"
+    Sensitive Property: {}
+Parameter Context Name: inherited-context
+      )", encrypted_parameter_value, encrypted_sensitive_property_value);
+
+  std::unique_ptr<core::ProcessGroup> flow = 
yaml_config.getRootFromPayload(TEST_CONFIG_YAML);
+  REQUIRE(flow);
+  auto* proc = flow->findProcessorByName("DummyFlowYamlProcessor");
+  REQUIRE(proc);
+  REQUIRE(proc->getProperty("Simple Property") == "old_value");
+  REQUIRE(proc->getProperty("Sensitive Property") == "value1");
+}
+
+TEST_CASE("Parameter context can not inherit from a itself", 
"[YamlConfiguration]") {
+  ConfigurationTestController test_controller;
+  core::YamlConfiguration yaml_config(test_controller.getContext());
+
+  static const std::string TEST_CONFIG_YAML =
+      R"(
+MiNiFi Config Version: 3
+Flow Controller:
+  name: flowconfig
+Parameter Contexts:
+  - id: 521e10b7-8e00-3188-9a27-476cca376351
+    name: base-context
+    description: my base parameter context
+    Parameters:
+    - name: my_old_parameter
+      description: ''
+      sensitive: false
+      value: old_value
+    Inherited Parameter Contexts:
+    - base-context
+Processors:
+- id: b0c04f28-0158-1000-0000-000000000000
+  name: DummyFlowYamlProcessor
+  class: org.apache.nifi.processors.DummyFlowYamlProcessor
+  max concurrent tasks: 1
+  scheduling strategy: TIMER_DRIVEN
+  scheduling period: 1 sec
+  auto-terminated relationships list: [success]
+  Properties:
+    Simple Property: "#{my_old_parameter}"
+Parameter Context Name: inherited-context
+      )";
+
+  REQUIRE_THROWS_WITH(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), 
"Inherited parameter context 'base-context' cannot be the same as the parameter 
context!");
+}
+
+TEST_CASE("Parameter context can not inherit from non-existing parameter 
context", "[YamlConfiguration]") {
+  ConfigurationTestController test_controller;
+  core::YamlConfiguration yaml_config(test_controller.getContext());
+
+  static const std::string TEST_CONFIG_YAML =
+      R"(
+MiNiFi Config Version: 3
+Flow Controller:
+  name: flowconfig
+Parameter Contexts:
+  - id: 521e10b7-8e00-3188-9a27-476cca376351
+    name: base-context
+    description: my base parameter context
+    Parameters:
+    - name: my_old_parameter
+      description: ''
+      sensitive: false
+      value: old_value
+    Inherited Parameter Contexts: [unknown]
+Processors:
+- id: b0c04f28-0158-1000-0000-000000000000
+  name: DummyFlowYamlProcessor
+  class: org.apache.nifi.processors.DummyFlowYamlProcessor
+  max concurrent tasks: 1
+  scheduling strategy: TIMER_DRIVEN
+  scheduling period: 1 sec
+  auto-terminated relationships list: [success]
+  Properties:
+    Simple Property: "#{my_old_parameter}"
+Parameter Context Name: inherited-context
+      )";
+
+  REQUIRE_THROWS_WITH(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), 
"Inherited parameter context 'unknown' does not exist!");
+}
+
+TEST_CASE("Cycles are not allowed in parameter context inheritance", 
"[YamlConfiguration]") {
+  ConfigurationTestController test_controller;
+  core::YamlConfiguration yaml_config(test_controller.getContext());
+
+  static const std::string TEST_CONFIG_YAML =
+      R"(
+MiNiFi Config Version: 3
+Flow Controller:
+  name: flowconfig
+Parameter Contexts:
+  - id: 123e10b7-8e00-3188-9a27-476cca376351
+    name: a-context
+    description: ''
+    Parameters:
+    - name: a_parameter
+      description: ''
+      sensitive: false
+      value: a_value
+    Inherited Parameter Contexts:
+    - c-context
+  - id: 456e10b7-8e00-3188-9a27-476cca376351
+    name: b-context
+    description: ''
+    Parameters:
+    - name: b_parameter
+      description: ''
+      sensitive: false
+      value: b_value
+    Inherited Parameter Contexts:
+    - a-context
+  - id: 789e10b7-8e00-3188-9a27-476cca376351
+    name: c-context
+    description: ''
+    Parameters:
+    - name: c_parameter
+      description: ''
+      sensitive: false
+      value: c_value
+    Inherited Parameter Contexts:
+    - d-context
+    - b-context
+  - id: 101e10b7-8e00-3188-9a27-476cca376351
+    name: d-context
+    description: ''
+    Parameters:
+    - name: d_parameter
+      description: ''
+      sensitive: false
+      value: d_value
+    Inherited Parameter Contexts: []
+Processors:
+- id: b0c04f28-0158-1000-0000-000000000000
+  name: DummyFlowYamlProcessor
+  class: org.apache.nifi.processors.DummyFlowYamlProcessor
+  max concurrent tasks: 1
+  scheduling strategy: TIMER_DRIVEN
+  scheduling period: 1 sec
+  auto-terminated relationships list: [success]
+  Properties:
+    Simple Property: "#{{my_old_parameter}}"
+    Sensitive Property: {}
+Parameter Context Name: inherited-context
+      )";
+
+  REQUIRE_THROWS_AS(yaml_config.getRootFromPayload(TEST_CONFIG_YAML), 
std::invalid_argument);
+  REQUIRE(minifi::test::utils::verifyLogLinePresenceInPollTime(0s, "Circular 
references in Parameter Context inheritance are not allowed. Inheritance cycle 
was detected in parameter context"));
+}
+
+TEST_CASE("Parameter context inheritance order is respected", 
"[YamlConfiguration]") {
+  ConfigurationTestController test_controller;
+  core::YamlConfiguration yaml_config(test_controller.getContext());
+
+  static const std::string TEST_CONFIG_YAML =
+      R"(
+MiNiFi Config Version: 3
+Flow Controller:
+  name: flowconfig
+Parameter Contexts:
+  - id: 721e10b7-8e00-3188-9a27-476cca376978
+    name: a-context
+    description: ''
+    Parameters:
+    - name: a_parameter
+      description: ''
+      sensitive: false
+      value: 1
+    - name: b_parameter
+      description: ''
+      sensitive: false
+      value: 2
+  - id: 521e10b7-8e00-3188-9a27-476cca376351
+    name: b-context
+    description: ''
+    Parameters:
+    - name: b_parameter
+      description: ''
+      sensitive: false
+      value: 3
+    - name: c_parameter
+      description: ''
+      sensitive: false
+      value: 4
+  - id: 123e10b7-8e00-3188-9a27-476cca376351
+    name: c-context
+    description: ''
+    Parameters:
+    - name: c_parameter
+      description: ''
+      sensitive: false
+      value: 5
+    Inherited Parameter Contexts:
+    - b-context
+    - a-context
+Processors:
+- id: b0c04f28-0158-1000-0000-000000000000
+  name: DummyFlowYamlProcessor
+  class: org.apache.nifi.processors.DummyFlowYamlProcessor
+  max concurrent tasks: 1
+  scheduling strategy: TIMER_DRIVEN
+  scheduling period: 1 sec
+  auto-terminated relationships list: [success]
+  Properties:
+    My A Property: "#{a_parameter}"
+    My B Property: "#{b_parameter}"
+    My C Property: "#{c_parameter}"
+Parameter Context Name: c-context
+      )";
+
+  std::unique_ptr<core::ProcessGroup> flow = 
yaml_config.getRootFromPayload(TEST_CONFIG_YAML);
+  REQUIRE(flow);
+  auto* proc = flow->findProcessorByName("DummyFlowYamlProcessor");
+  REQUIRE(proc);
+  std::string value;
+  REQUIRE(proc->getDynamicProperty("My A Property", value));
+  CHECK(value == "1");
+  REQUIRE(proc->getDynamicProperty("My B Property", value));
+  CHECK(value == "3");
+  REQUIRE(proc->getDynamicProperty("My C Property", value));
+  CHECK(value == "5");
+}
+
 }  // namespace org::apache::nifi::minifi::test
diff --git a/libminifi/include/core/ParameterContext.h 
b/libminifi/include/core/ParameterContext.h
index 825d9ed1d..f9bd64935 100644
--- a/libminifi/include/core/ParameterContext.h
+++ b/libminifi/include/core/ParameterContext.h
@@ -53,13 +53,23 @@ class ParameterContext : public CoreComponentImpl {
 
   void addParameter(const Parameter &parameter);
   std::optional<Parameter> getParameter(const std::string &name) const;
+
   const std::unordered_map<std::string, Parameter>& getParameters() const {
     return parameters_;
   }
 
+  void addInheritedParameterContext(gsl::not_null<ParameterContext*> 
parameter_context) {
+    inherited_parameter_contexts_.push_back(parameter_context);
+  }
+
+  const std::vector<gsl::not_null<ParameterContext*>>& 
getInheritedParameterContexts() const {
+    return inherited_parameter_contexts_;
+  }
+
  private:
   std::string description_;
   std::unordered_map<std::string, Parameter> parameters_;
+  std::vector<gsl::not_null<ParameterContext*>> inherited_parameter_contexts_;
 };
 
 }  // namespace org::apache::nifi::minifi::core
diff --git a/libminifi/include/core/flow/FlowSchema.h 
b/libminifi/include/core/flow/FlowSchema.h
index 6cbad427a..9860cb86e 100644
--- a/libminifi/include/core/flow/FlowSchema.h
+++ b/libminifi/include/core/flow/FlowSchema.h
@@ -86,6 +86,7 @@ struct FlowSchema {
   Keys value;
   Keys parameter_context_name;
   Keys sensitive;
+  Keys inherited_parameter_contexts;
 
   static FlowSchema getDefault();
   static FlowSchema getNiFiFlowJson();
diff --git a/libminifi/include/core/flow/StructuredConfiguration.h 
b/libminifi/include/core/flow/StructuredConfiguration.h
index 23cbf2847..52b49f69e 100644
--- a/libminifi/include/core/flow/StructuredConfiguration.h
+++ b/libminifi/include/core/flow/StructuredConfiguration.h
@@ -234,6 +234,7 @@ class StructuredConfiguration : public FlowConfiguration {
    * @param reason
    */
   void raiseComponentError(const std::string &component_name, const 
std::string &section, const std::string &reason) const;
+  void verifyNoInheritanceCycles() const;
 };
 
 }  // namespace org::apache::nifi::minifi::core::flow
diff --git a/libminifi/src/core/ParameterContext.cpp 
b/libminifi/src/core/ParameterContext.cpp
index b4501930f..460507c92 100644
--- a/libminifi/src/core/ParameterContext.cpp
+++ b/libminifi/src/core/ParameterContext.cpp
@@ -31,6 +31,11 @@ std::optional<Parameter> 
ParameterContext::getParameter(const std::string &name)
   if (it != parameters_.end()) {
     return it->second;
   }
+  for (const auto& parameter_context : inherited_parameter_contexts_) {
+    if (auto parameter = parameter_context->getParameter(name)) {
+      return parameter;
+    }
+  }
   return std::nullopt;
 }
 
diff --git a/libminifi/src/core/flow/FlowSchema.cpp 
b/libminifi/src/core/flow/FlowSchema.cpp
index 4ab3aba0b..b58edc698 100644
--- a/libminifi/src/core/flow/FlowSchema.cpp
+++ b/libminifi/src/core/flow/FlowSchema.cpp
@@ -82,7 +82,8 @@ FlowSchema FlowSchema::getDefault() {
       .description = {"description"},
       .value = {"value"},
       .parameter_context_name = {"Parameter Context Name"},
-      .sensitive = {"sensitive"}
+      .sensitive = {"sensitive"},
+      .inherited_parameter_contexts = {"Inherited Parameter Contexts"}
   };
 }
 
@@ -149,7 +150,8 @@ FlowSchema FlowSchema::getNiFiFlowJson() {
       .description = {"description"},
       .value = {"value"},
       .parameter_context_name = {"parameterContextName"},
-      .sensitive = {"sensitive"}
+      .sensitive = {"sensitive"},
+      .inherited_parameter_contexts = {"inheritedParameterContexts"}
   };
 }
 
diff --git a/libminifi/src/core/flow/StructuredConfiguration.cpp 
b/libminifi/src/core/flow/StructuredConfiguration.cpp
index d79550b46..e09745957 100644
--- a/libminifi/src/core/flow/StructuredConfiguration.cpp
+++ b/libminifi/src/core/flow/StructuredConfiguration.cpp
@@ -139,6 +139,41 @@ std::unique_ptr<core::ProcessGroup> 
StructuredConfiguration::getRootFrom(const N
   }
 }
 
+namespace {
+bool hasInheritanceCycle(const ParameterContext& parameter_context, 
std::unordered_set<std::string>& visited_parameter_contexts, 
std::unordered_set<std::string>& current_stack) {
+  if (current_stack.contains(parameter_context.getName())) {
+    return true;
+  }
+
+  if (visited_parameter_contexts.contains(parameter_context.getName())) {
+    return false;
+  }
+
+  current_stack.insert(parameter_context.getName());
+  visited_parameter_contexts.insert(parameter_context.getName());
+
+  for (const auto& inherited_parameter_context : 
parameter_context.getInheritedParameterContexts()) {
+    if (hasInheritanceCycle(*inherited_parameter_context, 
visited_parameter_contexts, current_stack)) {
+      return true;
+    }
+  }
+
+  current_stack.erase(parameter_context.getName());
+
+  return false;
+}
+}  // namespace
+
+void StructuredConfiguration::verifyNoInheritanceCycles() const {
+  std::unordered_set<std::string> visited_parameter_contexts;
+  std::unordered_set<std::string> current_stack;
+  for (const auto& [parameter_context_name, parameter_context] : 
parameter_contexts_) {
+    if (hasInheritanceCycle(*parameter_context, visited_parameter_contexts, 
current_stack)) {
+      throw std::invalid_argument("Circular references in Parameter Context 
inheritance are not allowed. Inheritance cycle was detected in parameter 
context '" + parameter_context_name + "'");
+    }
+  }
+}
+
 void StructuredConfiguration::parseParameterContexts(const Node& 
parameter_contexts_node) {
   if (!parameter_contexts_node || !parameter_contexts_node.isSequence()) {
     return;
@@ -172,6 +207,27 @@ void StructuredConfiguration::parseParameterContexts(const 
Node& parameter_conte
 
     parameter_contexts_.emplace(name, 
gsl::make_not_null(std::move(parameter_context)));
   }
+
+  for (const auto& parameter_context_node : parameter_contexts_node) {
+    if (!isFieldPresent(parameter_context_node, 
schema_.inherited_parameter_contexts[0])) {
+      continue;
+    }
+    auto inherited_parameters_node = 
parameter_context_node[schema_.inherited_parameter_contexts];
+    for (const auto& inherited_parameter_context_name : 
inherited_parameters_node) {
+      auto name = inherited_parameter_context_name.getString().value();
+      if (parameter_contexts_.find(name) == parameter_contexts_.end()) {
+        throw std::invalid_argument("Inherited parameter context '" + name + 
"' does not exist!");
+      }
+
+      auto parameter_context_name = 
parameter_context_node[schema_.name].getString().value();
+      if (parameter_context_name == name) {
+        throw std::invalid_argument("Inherited parameter context '" + name + 
"' cannot be the same as the parameter context!");
+      }
+      gsl::not_null<ParameterContext*> context = 
gsl::make_not_null(parameter_contexts_.at(name).get());
+      
parameter_contexts_.at(parameter_context_name)->addInheritedParameterContext(context);
+    }
+  }
+  verifyNoInheritanceCycles();
 }
 
 void StructuredConfiguration::parseProcessorNode(const Node& processors_node, 
core::ProcessGroup* parentGroup) {

Reply via email to