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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new fcbc059157 NIFI-15541 Fixed Parameter Context binding for new Process 
Groups on Version Upgrades (#10844)
fcbc059157 is described below

commit fcbc059157f8b8fa82b1b46d7692c68ef039f1f9
Author: Pierre Villard <[email protected]>
AuthorDate: Fri Feb 6 06:34:58 2026 +0100

    NIFI-15541 Fixed Parameter Context binding for new Process Groups on 
Version Upgrades (#10844)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../StandardVersionedComponentSynchronizer.java    |  59 ++++++-
 .../nifi/parameter/ParameterContextNameUtils.java  | 141 +++++++++++++++
 .../ParameterContextNameCollisionResolver.java     |  44 ++---
 .../registry/ParameterContextPreservationIT.java   | 191 +++++++++++++++++++++
 4 files changed, 398 insertions(+), 37 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
index 0d9775fadd..ef54e189c4 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
@@ -92,6 +92,7 @@ import 
org.apache.nifi.migration.StandardControllerServiceFactory;
 import org.apache.nifi.parameter.Parameter;
 import org.apache.nifi.parameter.ParameterContext;
 import org.apache.nifi.parameter.ParameterContextManager;
+import org.apache.nifi.parameter.ParameterContextNameUtils;
 import org.apache.nifi.parameter.ParameterDescriptor;
 import org.apache.nifi.parameter.ParameterProviderConfiguration;
 import org.apache.nifi.parameter.ParameterReferenceManager;
@@ -2152,15 +2153,29 @@ public class StandardVersionedComponentSynchronizer 
implements VersionedComponen
                 createMissingParameterProvider(versionedParameterContext, 
versionedParameterContext.getParameterProvider(), parameterProviderReferences, 
componentIdGenerator);
                 if (currentParamContext == null) {
                     // Create a new Parameter Context based on the parameters 
provided
-                    final ParameterContext contextByName = 
getParameterContextByName(versionedParameterContext.getName());
                     final ParameterContext selectedParameterContext;
-                    if (contextByName == null) {
-                        final String parameterContextId = 
componentIdGenerator.generateUuid(versionedParameterContext.getName(),
-                                versionedParameterContext.getName(), 
versionedParameterContext.getName());
-                        selectedParameterContext = 
createParameterContext(versionedParameterContext, parameterContextId, 
versionedParameterContexts,
-                                parameterProviderReferences, 
componentIdGenerator);
+
+                    // Check if the parent group has a parameter context that 
corresponds to the same
+                    // versioned parameter context. If so, we should use the 
parent's context to maintain
+                    // consistency within this flow instance. This is 
important during flow version upgrades
+                    // where new child process groups are added - they should 
use the same parameter context
+                    // as their parent, not a different one that happens to 
match by name.
+                    final ParameterContext parentParameterContext = 
findMatchingParentParameterContext(group, versionedParameterContext.getName());
+                    if (parentParameterContext == null) {
+                        // Fall back to existing behavior: look up by name or 
create new
+                        final ParameterContext contextByName = 
getParameterContextByName(versionedParameterContext.getName());
+                        if (contextByName == null) {
+                            final String parameterContextId = 
componentIdGenerator.generateUuid(versionedParameterContext.getName(),
+                                    versionedParameterContext.getName(), 
versionedParameterContext.getName());
+                            selectedParameterContext = 
createParameterContext(versionedParameterContext, parameterContextId, 
versionedParameterContexts,
+                                    parameterProviderReferences, 
componentIdGenerator);
+                        } else {
+                            selectedParameterContext = contextByName;
+                            addMissingConfiguration(versionedParameterContext, 
selectedParameterContext, versionedParameterContexts, 
parameterProviderReferences, componentIdGenerator);
+                        }
                     } else {
-                        selectedParameterContext = contextByName;
+                        selectedParameterContext = parentParameterContext;
+                        // Ensure the parent's context has all the parameters 
from the versioned context
                         addMissingConfiguration(versionedParameterContext, 
selectedParameterContext, versionedParameterContexts, 
parameterProviderReferences, componentIdGenerator);
                     }
 
@@ -2173,6 +2188,36 @@ public class StandardVersionedComponentSynchronizer 
implements VersionedComponen
         }
     }
 
+    /**
+     * Finds a parameter context from the parent group hierarchy that 
corresponds to the given versioned
+     * parameter context name. This is used to ensure that when a new child 
process group is added during
+     * a flow version upgrade, it uses the same parameter context as its 
parent (if they both reference
+     * the same parameter context in the versioned flow), rather than looking 
up by name globally which
+     * could result in using a different parameter context with the same base 
name.
+     *
+     * @param group the process group being updated
+     * @param versionedParameterContextName the name of the parameter context 
in the versioned flow
+     * @return the matching parent parameter context, or null if not found
+     */
+    private ParameterContext findMatchingParentParameterContext(final 
ProcessGroup group, final String versionedParameterContextName) {
+        ProcessGroup parent = group.getParent();
+        while (parent != null) {
+            final ParameterContext parentContext = 
parent.getParameterContext();
+            if (parentContext != null) {
+                // Check if the parent's context corresponds to the same 
versioned parameter context name.
+                // The parent's context name might be the exact name or have a 
suffix like " (1)", " (2)", etc.
+                // if it was created during an import with REPLACE strategy.
+                final String parentContextName = parentContext.getName();
+                if (parentContextName.equals(versionedParameterContextName)
+                        || 
ParameterContextNameUtils.isNameWithSuffix(parentContextName, 
versionedParameterContextName)) {
+                    return parentContext;
+                }
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
     private void createMissingParameterProvider(final 
VersionedParameterContext versionedParameterContext, final String 
parameterProviderId,
                                                 final Map<String, 
ParameterProviderReference> parameterProviderReferences, final 
ComponentIdGenerator componentIdGenerator) {
         String parameterProviderIdToSet = parameterProviderId;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/ParameterContextNameUtils.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/ParameterContextNameUtils.java
new file mode 100644
index 0000000000..bd4b2e36de
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/ParameterContextNameUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+package org.apache.nifi.parameter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class for handling parameter context name patterns, particularly for
+ * names with numeric suffixes like "Context (1)", "Context (2)", etc.
+ *
+ * <p>When importing versioned flows with the REPLACE parameter context 
strategy,
+ * NiFi creates new parameter contexts with suffixed names if a context with 
the
+ * same name already exists. This utility provides methods to parse and match
+ * these naming patterns consistently across the codebase.</p>
+ */
+public final class ParameterContextNameUtils {
+
+    private static final String PATTERN_GROUP_NAME = "name";
+    private static final String PATTERN_GROUP_INDEX = "index";
+
+    /**
+     * Pattern that matches parameter context names, optionally with a numeric 
suffix.
+     * Examples: "MyContext", "MyContext (1)", "MyContext (2)", "My Context 
(10)"
+     */
+    private static final String LINEAGE_FORMAT = "^(?<" + PATTERN_GROUP_NAME + 
">.+?)( \\((?<" + PATTERN_GROUP_INDEX + ">[0-9]+)\\))?$";
+    private static final Pattern LINEAGE_PATTERN = 
Pattern.compile(LINEAGE_FORMAT);
+
+    /**
+     * Format string for creating parameter context names with a numeric 
suffix.
+     * Usage: String.format(NAME_FORMAT, baseName, index) produces "baseName 
(index)"
+     */
+    public static final String NAME_FORMAT = "%s (%d)";
+
+    private ParameterContextNameUtils() {
+        // Utility class - prevent instantiation
+    }
+
+    /**
+     * Checks if the given context name matches a base name with a numeric 
suffix.
+     * For example, "P (1)" or "P (2)" would match base name "P".
+     *
+     * @param contextName the actual parameter context name to check
+     * @param baseName the base parameter context name (without suffix)
+     * @return true if contextName equals baseName with a suffix like " (n)", 
false otherwise
+     */
+    public static boolean isNameWithSuffix(final String contextName, final 
String baseName) {
+        if (contextName == null || baseName == null) {
+            return false;
+        }
+
+        final Matcher matcher = LINEAGE_PATTERN.matcher(contextName);
+        if (!matcher.matches()) {
+            return false;
+        }
+
+        final String extractedBaseName = matcher.group(PATTERN_GROUP_NAME);
+        final String indexGroup = matcher.group(PATTERN_GROUP_INDEX);
+
+        // Must have a suffix (index) and the base name must match
+        return indexGroup != null && baseName.equals(extractedBaseName);
+    }
+
+    /**
+     * Extracts the base name from a parameter context name, removing any 
numeric suffix.
+     * For example, "MyContext (1)" returns "MyContext", and "MyContext" 
returns "MyContext".
+     *
+     * @param name the parameter context name
+     * @return the base name without the suffix, or the original name if no 
suffix exists
+     * @throws IllegalArgumentException if the name cannot be parsed
+     */
+    public static String extractBaseName(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Parameter context name cannot 
be null");
+        }
+
+        final Matcher matcher = LINEAGE_PATTERN.matcher(name);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Parameter context name \"" + 
name + "\" cannot be parsed");
+        }
+
+        return matcher.group(PATTERN_GROUP_NAME);
+    }
+
+    /**
+     * Extracts the suffix index from a parameter context name.
+     * For example, "MyContext (3)" returns 3, and "MyContext" returns -1.
+     *
+     * @param name the parameter context name
+     * @return the suffix index, or -1 if the name has no suffix
+     * @throws IllegalArgumentException if the name cannot be parsed
+     */
+    public static int extractSuffixIndex(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Parameter context name cannot 
be null");
+        }
+
+        final Matcher matcher = LINEAGE_PATTERN.matcher(name);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Parameter context name \"" + 
name + "\" cannot be parsed");
+        }
+
+        final String indexGroup = matcher.group(PATTERN_GROUP_INDEX);
+        return indexGroup == null ? -1 : Integer.parseInt(indexGroup);
+    }
+
+    /**
+     * Creates a parameter context name with a numeric suffix.
+     *
+     * @param baseName the base name
+     * @param index the suffix index
+     * @return the formatted name, e.g., "baseName (index)"
+     */
+    public static String createNameWithSuffix(final String baseName, final int 
index) {
+        return String.format(NAME_FORMAT, baseName, index);
+    }
+
+    /**
+     * Returns the compiled pattern for matching parameter context names with 
optional suffixes.
+     * This can be used for more complex matching scenarios.
+     *
+     * @return the compiled Pattern
+     */
+    public static Pattern getLineagePattern() {
+        return LINEAGE_PATTERN;
+    }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ParameterContextNameCollisionResolver.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ParameterContextNameCollisionResolver.java
index a66e4dad37..8da3113c0c 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ParameterContextNameCollisionResolver.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ParameterContextNameCollisionResolver.java
@@ -16,59 +16,43 @@
  */
 package org.apache.nifi.web.util;
 
+import org.apache.nifi.parameter.ParameterContextNameUtils;
 import org.apache.nifi.web.api.dto.ParameterContextDTO;
 import org.apache.nifi.web.api.entity.ParameterContextEntity;
 
 import java.util.Collection;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 public class ParameterContextNameCollisionResolver {
-    private static final String PATTERN_GROUP_NAME = "name";
-    private static final String PATTERN_GROUP_INDEX = "index";
-
-    private static final String LINEAGE_FORMAT = "^(?<" + PATTERN_GROUP_NAME + 
">.+?)( \\((?<" + PATTERN_GROUP_INDEX + ">[0-9]+)\\))?$";
-    private static final Pattern LINEAGE_PATTERN = 
Pattern.compile(LINEAGE_FORMAT);
-
-    private static final String NAME_FORMAT = "%s (%d)";
 
     public String resolveNameCollision(final String 
originalParameterContextName, final Collection<ParameterContextEntity> 
existingContexts) {
-        final Matcher lineageMatcher = 
LINEAGE_PATTERN.matcher(originalParameterContextName);
-
-        if (!lineageMatcher.matches()) {
-            throw new IllegalArgumentException("Existing Parameter Context 
name \"(" + originalParameterContextName + "\") cannot be processed");
-        }
-
-        final String lineName = lineageMatcher.group(PATTERN_GROUP_NAME);
-        final String originalIndex = lineageMatcher.group(PATTERN_GROUP_INDEX);
+        final String lineName = 
ParameterContextNameUtils.extractBaseName(originalParameterContextName);
+        final int originalIndex = 
ParameterContextNameUtils.extractSuffixIndex(originalParameterContextName);
 
         // Candidates cannot be cached because new context might be added 
between calls
         final Set<ParameterContextDTO> candidates = existingContexts
                 .stream()
-                .map(pc -> pc.getComponent())
+                .map(ParameterContextEntity::getComponent)
                 .filter(dto -> dto.getName().startsWith(lineName))
                 .collect(Collectors.toSet());
 
-        int biggestIndex = (originalIndex == null) ? 0 : 
Integer.valueOf(originalIndex);
+        int biggestIndex = Math.max(originalIndex, 0);
 
         for (final ParameterContextDTO candidate : candidates) {
-            final Matcher matcher = 
LINEAGE_PATTERN.matcher(candidate.getName());
-
-            if (matcher.matches() && 
lineName.equals(matcher.group(PATTERN_GROUP_NAME))) {
-                final String indexGroup = matcher.group(PATTERN_GROUP_INDEX);
-
-                if (indexGroup != null) {
-                    int biggestIndexCandidate = Integer.valueOf(indexGroup);
-
-                    if (biggestIndexCandidate > biggestIndex) {
-                        biggestIndex = biggestIndexCandidate;
+            try {
+                final String candidateBaseName = 
ParameterContextNameUtils.extractBaseName(candidate.getName());
+                if (lineName.equals(candidateBaseName)) {
+                    final int candidateIndex = 
ParameterContextNameUtils.extractSuffixIndex(candidate.getName());
+                    if (candidateIndex > biggestIndex) {
+                        biggestIndex = candidateIndex;
                     }
                 }
+            } catch (final IllegalArgumentException ignored) {
+                // Candidate name doesn't match expected pattern, skip it
             }
         }
 
-        return String.format(NAME_FORMAT, lineName, biggestIndex + 1);
+        return ParameterContextNameUtils.createNameWithSuffix(lineName, 
biggestIndex + 1);
     }
 }
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
new file mode 100644
index 0000000000..41c75af918
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.registry;
+
+import org.apache.nifi.tests.system.NiFiClientUtil;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.client.NiFiClientException;
+import org.apache.nifi.web.api.dto.ProcessGroupDTO;
+import org.apache.nifi.web.api.dto.VersionControlInformationDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
+import org.apache.nifi.web.api.entity.ParameterContextEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.api.entity.VersionControlInformationEntity;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * System test to verify that parameter context bindings are preserved during 
versioned flow upgrades
+ * when new process groups are added.
+ *
+ * This test reproduces a bug where:
+ * 1. v1: Process Group A with Parameter Context P, containing only a 
Processor X using param1
+ * 2. v2: Process Group A with Parameter Context P, now containing a NEW 
Process Group B also attached to P
+ * 3. When checking out v1 twice with "do not keep parameter context":
+ *    - First checkout creates A1 with Parameter Context P
+ *    - Second checkout creates A2 with Parameter Context P (1) since P 
already exists
+ * 4. When upgrading A2 from v1 to v2, the newly added Process Group B 
incorrectly gets
+ *    bound to P instead of P (1)
+ *
+ * The expectation is that the new Process Group B should be bound to P (1), 
the same
+ * parameter context that its parent A2 uses.
+ */
+class ParameterContextPreservationIT extends NiFiSystemIT {
+    private static final String TEST_FLOWS_BUCKET = "test-flows";
+    private static final String PARAMETER_CONTEXT_NAME = "P";
+    private static final String PARAMETER_NAME = "param1";
+    private static final String PARAMETER_VALUE = "value1";
+    private static final String PARAMETER_REFERENCE = "#{" + PARAMETER_NAME + 
"}";
+    private static final String GROUP_A_NAME = "A";
+    private static final String GROUP_B_NAME = "B";
+    private static final String FLOW_NAME = "FlowWithParameterContext";
+    private static final String PROCESSOR_TYPE = "GenerateFlowFile";
+    private static final String PROCESSOR_PROPERTY_TEXT = "Text";
+    private static final String RELATIONSHIP_SUCCESS = "success";
+    private static final String VERSION_1 = "1";
+    private static final String VERSION_2 = "2";
+
+    @Test
+    void testNewProcessGroupUsesCorrectParameterContextDuringUpgrade() throws 
NiFiClientException, IOException, InterruptedException {
+        final FlowRegistryClientEntity clientEntity = registerClient();
+        final NiFiClientUtil util = getClientUtil();
+
+        // Step 1: Create Parameter Context "P" with param1
+        final ParameterContextEntity paramContextP = 
util.createParameterContext(PARAMETER_CONTEXT_NAME, Map.of(PARAMETER_NAME, 
PARAMETER_VALUE));
+
+        // Step 2: Create v1 - Process Group A with just a Processor X using 
param1
+        final ProcessGroupEntity groupA = 
util.createProcessGroup(GROUP_A_NAME, "root");
+        util.setParameterContext(groupA.getId(), paramContextP);
+
+        final ProcessorEntity processorX = 
util.createProcessor(PROCESSOR_TYPE, groupA.getId());
+        util.updateProcessorProperties(processorX, 
Collections.singletonMap(PROCESSOR_PROPERTY_TEXT, PARAMETER_REFERENCE));
+        util.setAutoTerminatedRelationships(processorX, RELATIONSHIP_SUCCESS);
+
+        // Save as v1
+        final VersionControlInformationEntity vciV1 = 
util.startVersionControl(groupA, clientEntity, TEST_FLOWS_BUCKET, FLOW_NAME);
+        final String flowId = vciV1.getVersionControlInformation().getFlowId();
+
+        // Step 3: Create v2 - Add Process Group B inside A, also attached to P
+        final ProcessGroupEntity groupB = 
util.createProcessGroup(GROUP_B_NAME, groupA.getId());
+        util.setParameterContext(groupB.getId(), paramContextP);
+
+        // Add a processor in B that uses param1
+        final ProcessorEntity processorInB = 
util.createProcessor(PROCESSOR_TYPE, groupB.getId());
+        util.updateProcessorProperties(processorInB, 
Collections.singletonMap(PROCESSOR_PROPERTY_TEXT, PARAMETER_REFERENCE));
+        util.setAutoTerminatedRelationships(processorInB, 
RELATIONSHIP_SUCCESS);
+
+        // Save as v2
+        final ProcessGroupEntity refreshedGroupA = 
getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+        util.saveFlowVersion(refreshedGroupA, clientEntity, vciV1);
+
+        // Step 4: Clean up original flow
+        final ProcessGroupEntity groupAForStopVc = 
getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+        
getNifiClient().getVersionsClient().stopVersionControl(groupAForStopVc);
+        util.deleteAll(groupA.getId());
+        final ProcessGroupEntity groupAToDelete = 
getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+        
getNifiClient().getProcessGroupClient().deleteProcessGroup(groupAToDelete);
+
+        final ParameterContextEntity contextToDelete = 
getNifiClient().getParamContextClient().getParamContext(paramContextP.getId(), 
false);
+        
getNifiClient().getParamContextClient().deleteParamContext(paramContextP.getId(),
+                String.valueOf(contextToDelete.getRevision().getVersion()));
+
+        // Step 5: Import v1 FIRST time - creates A1 with Parameter Context "P"
+        final ProcessGroupEntity importedGroup1 = 
importFlowWithReplaceParameterContext(
+                clientEntity.getId(), flowId, VERSION_1);
+
+        final ProcessGroupEntity fetchedGroup1 = 
getNifiClient().getProcessGroupClient().getProcessGroup(importedGroup1.getId());
+        final String paramContextId1 = 
fetchedGroup1.getComponent().getParameterContext().getId();
+
+        // Step 6: Import v1 SECOND time - creates A2 with Parameter Context 
"P (1)"
+        final ProcessGroupEntity importedGroup2 = 
importFlowWithReplaceParameterContext(
+                clientEntity.getId(), flowId, VERSION_1);
+
+        final ProcessGroupEntity fetchedGroup2 = 
getNifiClient().getProcessGroupClient().getProcessGroup(importedGroup2.getId());
+        final String paramContextId2 = 
fetchedGroup2.getComponent().getParameterContext().getId();
+
+        // Verify the two imports have DIFFERENT parameter contexts
+        assertNotEquals(paramContextId1, paramContextId2,
+                "Second import should have a different parameter context");
+
+        // Step 7: Upgrade A2 from v1 to v2 (this adds Process Group B)
+        util.changeFlowVersion(importedGroup2.getId(), VERSION_2);
+
+        // Step 8: Verify the NEW Process Group B uses the CORRECT parameter 
context
+        final ProcessGroupEntity upgradedGroup2 = 
getNifiClient().getProcessGroupClient().getProcessGroup(importedGroup2.getId());
+        final String upgradedA2ParamContextId = 
upgradedGroup2.getComponent().getParameterContext().getId();
+
+        // A2 should still use P (1)
+        assertEquals(paramContextId2, upgradedA2ParamContextId,
+                "After upgrade, A2 should still reference its original 
parameter context");
+
+        // Get the newly added Process Group B
+        final ProcessGroupEntity newGroupB = 
getNestedProcessGroup(upgradedGroup2, GROUP_B_NAME);
+        assertNotNull(newGroupB, "Process Group B should exist after upgrade");
+        assertNotNull(newGroupB.getComponent().getParameterContext(),
+                "Process Group B should have a parameter context assigned");
+
+        final String groupBParamContextId = 
newGroupB.getComponent().getParameterContext().getId();
+
+        // The NEW Process Group B should use the SAME parameter context as 
its parent A2 (P (1))
+        assertEquals(paramContextId2, groupBParamContextId,
+                "NEW Process Group B should use the same parameter context as 
its parent A2, not be bound to the first parameter context P");
+    }
+
+    private ProcessGroupEntity importFlowWithReplaceParameterContext(
+            final String registryClientId,
+            final String flowId,
+            final String version) throws NiFiClientException, IOException {
+
+        final VersionControlInformationDTO vci = new 
VersionControlInformationDTO();
+        vci.setBucketId(TEST_FLOWS_BUCKET);
+        vci.setFlowId(flowId);
+        vci.setVersion(version);
+        vci.setRegistryId(registryClientId);
+
+        final ProcessGroupDTO processGroupDto = new ProcessGroupDTO();
+        processGroupDto.setVersionControlInformation(vci);
+
+        final ProcessGroupEntity groupEntity = new ProcessGroupEntity();
+        groupEntity.setComponent(processGroupDto);
+        groupEntity.setRevision(getClientUtil().createNewRevision());
+
+        return 
getNifiClient().getProcessGroupClient().createProcessGroup("root", groupEntity, 
false);
+    }
+
+    private ProcessGroupEntity getNestedProcessGroup(ProcessGroupEntity 
parent, String name) throws NiFiClientException, IOException {
+        final ProcessGroupFlowEntity flowEntity = 
getNifiClient().getFlowClient().getProcessGroup(parent.getId());
+        final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
+
+        for (ProcessGroupEntity childGroup : flowDto.getProcessGroups()) {
+            if (name.equals(childGroup.getComponent().getName())) {
+                return 
getNifiClient().getProcessGroupClient().getProcessGroup(childGroup.getId());
+            }
+        }
+        return null;
+    }
+}

Reply via email to