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

bbende 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 ffa30a17fb NIFI-15010 - FlowDifferenceFilters - better handle property 
renaming (#10340)
ffa30a17fb is described below

commit ffa30a17fb83b04abeb90c2cf8ac199a2412df1d
Author: Pierre Villard <[email protected]>
AuthorDate: Wed Sep 24 20:51:06 2025 +0200

    NIFI-15010 - FlowDifferenceFilters - better handle property renaming 
(#10340)
    
    Signed-off-by: Pierre Villard <[email protected]>
---
 .../apache/nifi/util/FlowDifferenceFilters.java    | 106 +++++++++++++++++++--
 .../nifi/util/TestFlowDifferenceFilters.java       |  51 ++++++++++
 2 files changed, 148 insertions(+), 9 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
index c701401e11..caf6ca1ecc 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/FlowDifferenceFilters.java
@@ -95,7 +95,8 @@ public class FlowDifferenceFilters {
             || isLogFileSuffixChange(difference)
             || isStaticPropertyRemoved(difference, flowManager)
             || isControllerServiceCreatedForNewProperty(difference, 
evaluatedContext)
-            || isPropertyParameterizationRename(difference, evaluatedContext);
+            || isPropertyParameterizationRename(difference, evaluatedContext)
+            || isPropertyRenameWithMatchingValue(difference, evaluatedContext);
     }
 
     private static boolean isSensitivePropertyDueToGhosting(final 
FlowDifference difference, final FlowManager flowManager) {
@@ -585,6 +586,8 @@ public class FlowDifferenceFilters {
         final Set<String> serviceIdsReferencedByNewProperties = new 
HashSet<>();
         final Map<String, List<PropertyDiffInfo>> parameterizedAddsByComponent 
= new HashMap<>();
         final Map<String, List<PropertyDiffInfo>> 
parameterizationRemovalsByComponent = new HashMap<>();
+        final Map<String, List<PropertyDiffInfo>> propertyAddsByComponent = 
new HashMap<>();
+        final Map<String, List<PropertyDiffInfo>> propertyRemovalsByComponent 
= new HashMap<>();
 
         for (final FlowDifference difference : differences) {
             if (difference.getDifferenceType() == 
DifferenceType.PROPERTY_ADDED) {
@@ -602,6 +605,20 @@ public class FlowDifferenceFilters {
                         }
                     }
                 }
+
+                final Optional<String> componentIdOptional = 
getComponentInstanceIdentifier(difference);
+                if (componentIdOptional.isPresent()) {
+                    final PropertyDiffInfo diffInfo = new 
PropertyDiffInfo(getPropertyValue(difference, false), difference);
+                    
propertyAddsByComponent.computeIfAbsent(componentIdOptional.get(), key -> new 
ArrayList<>()).add(diffInfo);
+                }
+            }
+
+            if (difference.getDifferenceType() == 
DifferenceType.PROPERTY_REMOVED) {
+                final Optional<String> componentIdOptional = 
getComponentInstanceIdentifier(difference);
+                if (componentIdOptional.isPresent()) {
+                    final PropertyDiffInfo diffInfo = new 
PropertyDiffInfo(getPropertyValue(difference, true), difference);
+                    
propertyRemovalsByComponent.computeIfAbsent(componentIdOptional.get(), key -> 
new ArrayList<>()).add(diffInfo);
+                }
             } else if (difference.getDifferenceType() == 
DifferenceType.PROPERTY_PARAMETERIZED
                     || difference.getDifferenceType() == 
DifferenceType.PROPERTY_PARAMETERIZATION_REMOVED) {
 
@@ -676,14 +693,52 @@ public class FlowDifferenceFilters {
             }
         }
 
-        final EnvironmentalChangeContext environmentalChangeContext;
-        if (serviceIdsWithMatchingAdditions.isEmpty() && 
parameterizedPropertyRenameDifferences.isEmpty()) {
-            environmentalChangeContext = EnvironmentalChangeContext.empty();
-        } else {
-            environmentalChangeContext = new 
EnvironmentalChangeContext(serviceIdsWithMatchingAdditions, 
parameterizedPropertyRenameDifferences);
+        final Set<FlowDifference> propertyRenamesWithMatchingValues = new 
HashSet<>();
+        for (final Map.Entry<String, List<PropertyDiffInfo>> entry : 
propertyRemovalsByComponent.entrySet()) {
+            final String componentId = entry.getKey();
+            final List<PropertyDiffInfo> removals = entry.getValue();
+            final List<PropertyDiffInfo> additions = new 
ArrayList<>(propertyAddsByComponent.getOrDefault(componentId, 
Collections.emptyList()));
+            if (additions.isEmpty()) {
+                continue;
+            }
+
+            for (final PropertyDiffInfo removalInfo : removals) {
+                final Optional<String> removalValue = 
removalInfo.propertyValue();
+                if (removalValue.isEmpty()) {
+                    continue;
+                }
+
+                PropertyDiffInfo matchingAddition = null;
+                for (final Iterator<PropertyDiffInfo> iterator = 
additions.iterator(); iterator.hasNext();) {
+                    final PropertyDiffInfo additionInfo = iterator.next();
+                    final Optional<String> additionValue = 
additionInfo.propertyValue();
+                    if (additionValue.isEmpty()) {
+                        continue;
+                    }
+
+                    if (valuesMatch(removalValue, additionValue)) {
+                        matchingAddition = additionInfo;
+                        iterator.remove();
+                        break;
+                    }
+                }
+
+                if (matchingAddition != null) {
+                    final String removalField = 
removalInfo.difference().getFieldName().orElse(null);
+                    final String additionField = 
matchingAddition.difference().getFieldName().orElse(null);
+                    if (!Objects.equals(removalField, additionField)) {
+                        
propertyRenamesWithMatchingValues.add(removalInfo.difference());
+                        
propertyRenamesWithMatchingValues.add(matchingAddition.difference());
+                    }
+                }
+            }
         }
 
-        return environmentalChangeContext;
+        if (serviceIdsWithMatchingAdditions.isEmpty() && 
parameterizedPropertyRenameDifferences.isEmpty() && 
propertyRenamesWithMatchingValues.isEmpty()) {
+            return EnvironmentalChangeContext.empty();
+        }
+
+        return new EnvironmentalChangeContext(serviceIdsWithMatchingAdditions, 
parameterizedPropertyRenameDifferences, propertyRenamesWithMatchingValues);
     }
 
     public static boolean isControllerServiceCreatedForNewProperty(final 
FlowDifference difference, final EnvironmentalChangeContext context) {
@@ -739,6 +794,11 @@ public class FlowDifferenceFilters {
         return evaluatedContext.parameterizedPropertyRenames().isEmpty() ? 
false : evaluatedContext.parameterizedPropertyRenames().contains(difference);
     }
 
+    public static boolean isPropertyRenameWithMatchingValue(final 
FlowDifference difference, final EnvironmentalChangeContext context) {
+        final EnvironmentalChangeContext evaluatedContext = 
Objects.requireNonNull(context, "EnvironmentalChangeContext required");
+        return evaluatedContext.propertyRenamesWithMatchingValues().isEmpty() 
? false : 
evaluatedContext.propertyRenamesWithMatchingValues().contains(difference);
+    }
+
     private static Optional<String> getComponentInstanceIdentifier(final 
FlowDifference difference) {
         final Optional<String> identifierB = 
getComponentInstanceIdentifier(difference.getComponentB());
         if (identifierB.isPresent()) {
@@ -805,6 +865,27 @@ public class FlowDifferenceFilters {
         return parameterReference;
     }
 
+    private static Optional<String> getPropertyValue(final FlowDifference 
difference, final boolean fromComponentA) {
+        final Optional<String> fieldNameOptional = difference.getFieldName();
+        if (fieldNameOptional.isEmpty()) {
+            return Optional.empty();
+        }
+
+        final VersionedComponent component = fromComponentA ? 
difference.getComponentA() : difference.getComponentB();
+        final Map<String, String> properties = getProperties(component);
+        final String propertyValue = properties.get(fieldNameOptional.get());
+        if (propertyValue != null) {
+            return Optional.of(propertyValue);
+        }
+
+        final Object differenceValue = fromComponentA ? difference.getValueA() 
: difference.getValueB();
+        if (differenceValue instanceof String stringValue) {
+            return Optional.of(stringValue);
+        }
+
+        return Optional.empty();
+    }
+
     private static Map<String, String> getProperties(final VersionedComponent 
component) {
         final Map<String, String> properties;
 
@@ -852,15 +933,18 @@ public class FlowDifferenceFilters {
     }
 
     public static final class EnvironmentalChangeContext {
-        private static final EnvironmentalChangeContext EMPTY = new 
EnvironmentalChangeContext(Collections.emptySet(), Collections.emptySet());
+        private static final EnvironmentalChangeContext EMPTY = new 
EnvironmentalChangeContext(Collections.emptySet(), Collections.emptySet(), 
Collections.emptySet());
 
         private final Set<String> serviceIdsCreatedForNewProperties;
         private final Set<FlowDifference> parameterizedPropertyRenames;
+        private final Set<FlowDifference> propertyRenamesWithMatchingValues;
 
         private EnvironmentalChangeContext(final Set<String> 
serviceIdsCreatedForNewProperties,
-                                           final Set<FlowDifference> 
parameterizedPropertyRenames) {
+                                           final Set<FlowDifference> 
parameterizedPropertyRenames,
+                                           final Set<FlowDifference> 
propertyRenamesWithMatchingValues) {
             this.serviceIdsCreatedForNewProperties = 
Collections.unmodifiableSet(new HashSet<>(serviceIdsCreatedForNewProperties));
             this.parameterizedPropertyRenames = 
Collections.unmodifiableSet(new HashSet<>(parameterizedPropertyRenames));
+            this.propertyRenamesWithMatchingValues = 
Collections.unmodifiableSet(new HashSet<>(propertyRenamesWithMatchingValues));
         }
 
         static EnvironmentalChangeContext empty() {
@@ -874,5 +958,9 @@ public class FlowDifferenceFilters {
         Set<FlowDifference> parameterizedPropertyRenames() {
             return parameterizedPropertyRenames;
         }
+
+        Set<FlowDifference> propertyRenamesWithMatchingValues() {
+            return propertyRenamesWithMatchingValues;
+        }
     }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
index 31874c94fe..cb2430f570 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/util/TestFlowDifferenceFilters.java
@@ -356,6 +356,57 @@ public class TestFlowDifferenceFilters {
         assertTrue(FlowDifferenceFilters.isEnvironmentalChange(parameterized, 
null, flowManager, context));
     }
 
+    @Test
+    public void 
testPropertyRenameWithMatchingValueObservedAsEnvironmentalChange() {
+        final FlowManager flowManager = Mockito.mock(FlowManager.class);
+        final ProcessorNode processorNode = Mockito.mock(ProcessorNode.class);
+
+        final String processorInstanceId = "processor-instance";
+
+        
Mockito.when(flowManager.getProcessorNode(processorInstanceId)).thenReturn(processorNode);
+
+        final String groupId = "group-id";
+        final String versionedId = "versioned-id";
+        final String controllerServiceId = "service-id";
+        final String legacyPropertyName = "box-client-service";
+        final String renamedPropertyName = "Box Client Service";
+
+        final VersionedProcessor versionedProcessor = new VersionedProcessor();
+        versionedProcessor.setComponentType(ComponentType.PROCESSOR);
+        versionedProcessor.setIdentifier(versionedId);
+        versionedProcessor.setProperties(Map.of(legacyPropertyName, 
controllerServiceId));
+
+        final InstantiatedVersionedProcessor instantiatedProcessor = new 
InstantiatedVersionedProcessor(processorInstanceId, groupId);
+        instantiatedProcessor.setComponentType(ComponentType.PROCESSOR);
+        instantiatedProcessor.setIdentifier(versionedId);
+        instantiatedProcessor.setProperties(Map.of(renamedPropertyName, 
controllerServiceId));
+
+        final FlowDifference propertyRemoved = new StandardFlowDifference(
+                DifferenceType.PROPERTY_REMOVED,
+                versionedProcessor,
+                instantiatedProcessor,
+                legacyPropertyName,
+                controllerServiceId,
+                null,
+                "Legacy property removed");
+
+        final FlowDifference propertyAdded = new StandardFlowDifference(
+                DifferenceType.PROPERTY_ADDED,
+                versionedProcessor,
+                instantiatedProcessor,
+                renamedPropertyName,
+                null,
+                controllerServiceId,
+                "Renamed property added");
+
+        final List<FlowDifference> differences = List.of(propertyRemoved, 
propertyAdded);
+
+        final FlowDifferenceFilters.EnvironmentalChangeContext context = 
FlowDifferenceFilters.buildEnvironmentalChangeContext(differences, flowManager);
+
+        
assertTrue(FlowDifferenceFilters.isEnvironmentalChange(propertyRemoved, null, 
flowManager, context));
+        assertTrue(FlowDifferenceFilters.isEnvironmentalChange(propertyAdded, 
null, flowManager, context));
+    }
+
     @DynamicProperty(name = "Dynamic Property", value = "Value", description = 
"Allows dynamic properties")
     private static class DynamicAnnotationProcessor extends AbstractProcessor {
         @Override

Reply via email to