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

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


The following commit(s) were added to refs/heads/main by this push:
     new 8ea4965  NIFI-16253 Added deterministic order for Connector Property 
dependencies (#105)
8ea4965 is described below

commit 8ea4965df8391eac4147626d35db2124c22b8fc9
Author: David Handermann <[email protected]>
AuthorDate: Wed Aug 26 10:07:26 2026 -0500

    NIFI-16253 Added deterministic order for Connector Property dependencies 
(#105)
---
 .../components/connector/ConfigurationStep.java    |   5 +-
 .../connector/ConfigurationStepDependency.java     |   4 +-
 .../connector/ConnectorPropertyDependency.java     |   4 +-
 .../connector/ConnectorPropertyDescriptor.java     |   6 +-
 .../xml/XmlConnectorDocumentationWriter.java       |  27 ++++-
 .../xml/XmlConnectorDocumentationWriterTest.java   | 130 +++++++++++++++++++--
 6 files changed, 152 insertions(+), 24 deletions(-)

diff --git 
a/src/main/java/org/apache/nifi/components/connector/ConfigurationStep.java 
b/src/main/java/org/apache/nifi/components/connector/ConfigurationStep.java
index abbd84d..13c6efc 100644
--- a/src/main/java/org/apache/nifi/components/connector/ConfigurationStep.java
+++ b/src/main/java/org/apache/nifi/components/connector/ConfigurationStep.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -87,7 +88,7 @@ public final class ConfigurationStep {
         private String name;
         private String description;
         private List<ConnectorPropertyGroup> propertyGroups = 
Collections.emptyList();
-        private final Set<ConfigurationStepDependency> dependencies = new 
HashSet<>();
+        private final Set<ConfigurationStepDependency> dependencies = new 
LinkedHashSet<>();
 
         public Builder name(final String name) {
             this.name = name;
@@ -130,7 +131,7 @@ public final class ConfigurationStep {
             } else {
                 final Set<String> dependentValueSet = dependentValues.stream()
                     .map(DescribedValue::getValue)
-                    .collect(Collectors.toSet());
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
 
                 dependencies.add(new 
ConfigurationStepDependency(step.getName(), property.getName(), 
dependentValueSet));
             }
diff --git 
a/src/main/java/org/apache/nifi/components/connector/ConfigurationStepDependency.java
 
b/src/main/java/org/apache/nifi/components/connector/ConfigurationStepDependency.java
index f4e0ff9..0fa0421 100644
--- 
a/src/main/java/org/apache/nifi/components/connector/ConfigurationStepDependency.java
+++ 
b/src/main/java/org/apache/nifi/components/connector/ConfigurationStepDependency.java
@@ -17,7 +17,7 @@
 
 package org.apache.nifi.components.connector;
 
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 import java.util.Set;
 
@@ -43,7 +43,7 @@ public final class ConfigurationStepDependency {
     public ConfigurationStepDependency(final String stepName, final String 
propertyName, final Set<String> dependentValues) {
         this.stepName = Objects.requireNonNull(stepName, "Step name is 
required");
         this.propertyName = Objects.requireNonNull(propertyName, "Property 
name is required");
-        this.dependentValues = dependentValues == null ? null : new 
HashSet<>(dependentValues);
+        this.dependentValues = dependentValues == null ? null : new 
LinkedHashSet<>(dependentValues);
     }
 
     /**
diff --git 
a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDependency.java
 
b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDependency.java
index d2ecb79..faa26dc 100644
--- 
a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDependency.java
+++ 
b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDependency.java
@@ -17,6 +17,8 @@
 
 package org.apache.nifi.components.connector;
 
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 import java.util.Set;
 
@@ -26,7 +28,7 @@ public final class ConnectorPropertyDependency {
 
     public ConnectorPropertyDependency(final String propertyName, final 
Set<String> dependentValues) {
         this.propertyName = propertyName;
-        this.dependentValues = Set.copyOf(dependentValues);
+        this.dependentValues = Collections.unmodifiableSet(new 
LinkedHashSet<>(dependentValues));
     }
 
     public ConnectorPropertyDependency(final String propertyName) {
diff --git 
a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java
 
b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java
index 2072746..2bc8475 100644
--- 
a/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java
+++ 
b/src/main/java/org/apache/nifi/components/connector/ConnectorPropertyDescriptor.java
@@ -27,7 +27,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -278,7 +278,7 @@ public final class ConnectorPropertyDescriptor {
         private List<DescribedValue> allowableValues = null;
         private boolean allowableValuesFetchable = false;
         private final List<Validator> validators = new ArrayList<>();
-        private final Set<ConnectorPropertyDependency> dependencies = new 
HashSet<>();
+        private final Set<ConnectorPropertyDependency> dependencies = new 
LinkedHashSet<>();
 
         public Builder from(final ConnectorPropertyDescriptor other) {
             this.name = other.name;
@@ -438,7 +438,7 @@ public final class ConnectorPropertyDescriptor {
             } else {
                 final Set<String> dependentValueSet = dependentValues.stream()
                     .map(DescribedValue::getValue)
-                    .collect(Collectors.toSet());
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
 
                 dependencies.add(new 
ConnectorPropertyDependency(descriptor.getName(), dependentValueSet));
             }
diff --git 
a/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
 
b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
index 86712cd..b9cf150 100644
--- 
a/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
+++ 
b/src/main/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriter.java
@@ -20,9 +20,11 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.Function;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
@@ -52,6 +54,15 @@ import org.apache.nifi.documentation.ExtensionType;
  * removed from one incremental release to the next. Use at your own risk!
  */
 public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentationWriter {
+    private static final Comparator<ConfigurationStepDependency> 
STEP_DEPENDENCY_COMPARATOR =
+        Comparator.comparing(ConfigurationStepDependency::getStepName)
+            .thenComparing(ConfigurationStepDependency::getPropertyName)
+            .thenComparing(dependency -> 
getDependentValuesKey(dependency.getDependentValues()));
+
+    private static final Comparator<ConnectorPropertyDependency> 
PROPERTY_DEPENDENCY_COMPARATOR =
+        Comparator.comparing(ConnectorPropertyDependency::getPropertyName)
+            .thenComparing(dependency -> 
getDependentValuesKey(dependency.getDependentValues()));
+
     private final XMLStreamWriter writer;
 
     public XmlConnectorDocumentationWriter(final OutputStream out) throws 
XMLStreamException {
@@ -155,7 +166,7 @@ public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentat
         final Set<ConfigurationStepDependency> stepDependencies = 
step.getDependencies();
         if (stepDependencies != null && !stepDependencies.isEmpty()) {
             writeStartElement("stepDependencies");
-            for (final ConfigurationStepDependency dependency : 
stepDependencies) {
+            for (final ConfigurationStepDependency dependency : 
sorted(stepDependencies, STEP_DEPENDENCY_COMPARATOR)) {
                 writeConfigurationStepDependency(dependency);
             }
             writeEndElement();
@@ -182,7 +193,7 @@ public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentat
 
         final Set<String> dependentValues = dependency.getDependentValues();
         if (dependentValues != null && !dependentValues.isEmpty()) {
-            writeTextArray("dependentValues", "dependentValue", 
dependentValues);
+            writeTextArray("dependentValues", "dependentValue", new 
TreeSet<>(dependentValues));
         }
 
         writeEndElement();
@@ -238,7 +249,7 @@ public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentat
         final Set<ConnectorPropertyDependency> dependencies = 
property.getDependencies();
         if (dependencies != null && !dependencies.isEmpty()) {
             writeStartElement("dependencies");
-            for (final ConnectorPropertyDependency dependency : dependencies) {
+            for (final ConnectorPropertyDependency dependency : 
sorted(dependencies, PROPERTY_DEPENDENCY_COMPARATOR)) {
                 writePropertyDependency(dependency);
             }
             writeEndElement();
@@ -263,7 +274,7 @@ public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentat
 
         final Set<String> dependentValues = dependency.getDependentValues();
         if (dependentValues != null && !dependentValues.isEmpty()) {
-            writeTextArray("dependentValues", "dependentValue", 
dependentValues);
+            writeTextArray("dependentValues", "dependentValue", new 
TreeSet<>(dependentValues));
         }
 
         writeEndElement();
@@ -297,6 +308,14 @@ public class XmlConnectorDocumentationWriter extends 
AbstractConnectorDocumentat
         writeEndElement();
     }
 
+    private static <T> List<T> sorted(final Collection<T> values, final 
Comparator<T> comparator) {
+        return values.stream().sorted(comparator).toList();
+    }
+
+    private static String getDependentValuesKey(final Set<String> 
dependentValues) {
+        return dependentValues == null ? "" : String.join(",", new 
TreeSet<>(dependentValues));
+    }
+
     // Utility methods for XML writing
 
     private void writeStartElement(final String elementName) throws 
IOException {
diff --git 
a/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
 
b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
index 8c2401d..1790ccb 100644
--- 
a/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
+++ 
b/src/test/java/org/apache/nifi/documentation/xml/XmlConnectorDocumentationWriterTest.java
@@ -47,6 +47,7 @@ import javax.xml.transform.dom.DOMResult;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathNodes;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -55,11 +56,19 @@ import java.util.Map;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @ExtendWith(MockitoExtension.class)
 class XmlConnectorDocumentationWriterTest {
 
+    private static final String FIRST_DEPENDENT_VALUE = "SASL_PLAINTEXT";
+    private static final String SECOND_DEPENDENT_VALUE = "SASL_SSL";
+    private static final List<String> EXPECTED_DEPENDENT_VALUES = 
List.of(FIRST_DEPENDENT_VALUE, SECOND_DEPENDENT_VALUE);
+
+    private static final String FIRST_PROPERTY_NAME = "Alpha";
+    private static final String SECOND_PROPERTY_NAME = "Zulu";
+    private static final String FIRST_STEP_NAME = "Alpha Step";
+    private static final String SECOND_STEP_NAME = "Zulu Step";
+
     @Test
     void testWriteMinimalConnector() throws Exception {
         final Connector connector = new MinimalConnector();
@@ -333,18 +342,32 @@ class XmlConnectorDocumentationWriterTest {
         final Node dependentValuesNode = findNode(xpath, document);
         assertNotNull(dependentValuesNode);
 
-        final List<String> dependentValues = new ArrayList<>();
-        final NodeList valueNodes = dependentValuesNode.getChildNodes();
-        for (int i = 0; i < valueNodes.getLength(); i++) {
-            final Node valueNode = valueNodes.item(i);
-            if ("dependentValue".equals(valueNode.getNodeName())) {
-                dependentValues.add(valueNode.getTextContent());
-            }
-        }
+        assertEquals(List.of("advanced", "expert"), findValues(xpath + 
"/dependentValue", document));
+    }
+
+    @Test
+    void testWriteConnectorWithPropertyDependenciesSorted() throws Exception {
+        final Connector connector = new ConnectorWithUnsortedDependencies();
+        final Document document = writeDocumentation(connector);
+
+        final String propertyPath = 
"/extension/configurationSteps/configurationStep/propertyGroups/propertyGroup"
+            + "/properties/property[name='Dependent Property']";
+
+        assertEquals(List.of(FIRST_PROPERTY_NAME, SECOND_PROPERTY_NAME), 
findValues(propertyPath + "/dependencies/dependency/propertyName", document));
+        assertEquals(EXPECTED_DEPENDENT_VALUES,
+            findValues(propertyPath + 
"/dependencies/dependency[propertyName='Zulu']/dependentValues/dependentValue", 
document));
+    }
 
-        assertEquals(2, dependentValues.size());
-        assertTrue(dependentValues.contains("advanced"));
-        assertTrue(dependentValues.contains("expert"));
+    @Test
+    void testWriteConnectorWithStepDependenciesSorted() throws Exception {
+        final Connector connector = new 
ConnectorWithUnsortedStepDependencies();
+        final Document document = writeDocumentation(connector);
+
+        final String stepDependenciesPath = 
"/extension/configurationSteps/configurationStep[name='Dependent 
Step']/stepDependencies";
+
+        assertEquals(List.of(FIRST_STEP_NAME, SECOND_STEP_NAME), 
findValues(stepDependenciesPath + "/stepDependency/stepName", document));
+        assertEquals(EXPECTED_DEPENDENT_VALUES,
+            findValues(stepDependenciesPath + "/stepDependency[stepName='Zulu 
Step']/dependentValues/dependentValue", document));
     }
 
     @Test
@@ -459,6 +482,18 @@ class XmlConnectorDocumentationWriterTest {
         return path.evaluateExpression(expression, node, Node.class);
     }
 
+    private List<String> findValues(final String expression, final Node node) 
throws XPathExpressionException {
+        final XPathFactory factory = XPathFactory.newInstance();
+        final XPath path = factory.newXPath();
+        final XPathNodes nodes = path.evaluateExpression(expression, node, 
XPathNodes.class);
+
+        final List<String> values = new ArrayList<>();
+        for (final Node matchedNode : nodes) {
+            values.add(matchedNode.getTextContent());
+        }
+        return values;
+    }
+
     private void assertExtensionNameTypeFound(final Connector connector, final 
ExtensionType expectedExtensionType, final Document document) {
         assertNotNull(document);
 
@@ -785,6 +820,77 @@ class XmlConnectorDocumentationWriterTest {
         }
     }
 
+    private static class ConnectorWithUnsortedDependencies extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor zuluProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name(SECOND_PROPERTY_NAME)
+                .type(PropertyType.STRING)
+                .allowableValues(EXPECTED_DEPENDENT_VALUES)
+                .build();
+
+            final ConnectorPropertyDescriptor alphaProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name(FIRST_PROPERTY_NAME)
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConnectorPropertyDescriptor dependentProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Dependent Property")
+                .type(PropertyType.STRING)
+                .dependsOn(zuluProperty, SECOND_DEPENDENT_VALUE, 
FIRST_DEPENDENT_VALUE)
+                .dependsOn(alphaProperty)
+                .build();
+
+            final ConnectorPropertyGroup group = 
ConnectorPropertyGroup.builder()
+                .name("Settings")
+                .addProperty(zuluProperty)
+                .addProperty(alphaProperty)
+                .addProperty(dependentProperty)
+                .build();
+
+            return List.of(
+                new ConfigurationStep.Builder()
+                    .name("Settings Step")
+                    .propertyGroups(List.of(group))
+                    .build()
+            );
+        }
+    }
+
+    private static class ConnectorWithUnsortedStepDependencies extends 
MinimalConnector {
+        @Override
+        public List<ConfigurationStep> getConfigurationSteps() {
+            final ConnectorPropertyDescriptor zuluProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Zulu Property")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConfigurationStep zuluStep = new ConfigurationStep.Builder()
+                .name(SECOND_STEP_NAME)
+                
.propertyGroups(List.of(ConnectorPropertyGroup.builder().name("Zulu 
Group").addProperty(zuluProperty).build()))
+                .build();
+
+            final ConnectorPropertyDescriptor alphaProperty = new 
ConnectorPropertyDescriptor.Builder()
+                .name("Alpha Property")
+                .type(PropertyType.STRING)
+                .build();
+
+            final ConfigurationStep alphaStep = new ConfigurationStep.Builder()
+                .name(FIRST_STEP_NAME)
+                
.propertyGroups(List.of(ConnectorPropertyGroup.builder().name("Alpha 
Group").addProperty(alphaProperty).build()))
+                .build();
+
+            final ConfigurationStep dependentStep = new 
ConfigurationStep.Builder()
+                .name("Dependent Step")
+                
.propertyGroups(List.of(ConnectorPropertyGroup.builder().name("Dependent 
Group").build()))
+                .dependsOn(zuluStep, zuluProperty, SECOND_DEPENDENT_VALUE, 
FIRST_DEPENDENT_VALUE)
+                .dependsOn(alphaStep, alphaProperty)
+                .build();
+
+            return List.of(zuluStep, alphaStep, dependentStep);
+        }
+    }
+
     private static class ConnectorWithStepDependencyValues extends 
MinimalConnector {
         @Override
         public List<ConfigurationStep> getConfigurationSteps() {

Reply via email to