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

markap14 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 2feadfbe654 NIFI-15909 Resolve providerId from ConnectorConfigProvider 
values (#11209)
2feadfbe654 is described below

commit 2feadfbe6543b5b2d622cf1e697b41dfc08f6dd5
Author: Kevin Doran <[email protected]>
AuthorDate: Wed May 6 15:47:09 2026 -0400

    NIFI-15909 Resolve providerId from ConnectorConfigProvider values (#11209)
    
    * NIFI-15909 Resolve providerId from ConnectorConfigProvider values
    
    Resolve providerId on SECRET_REFERENCE entries returned by a 
ConnectorConfigurationProvider
    when only providerName is populated. The id is filled in only when the name 
unambiguously
    matches a single registered parameter provider; zero or ambiguous matches 
leave providerId
    null so the UI surfaces the SECRET_REFERENCE as invalid for 
re-configuration.
---
 .../connector/StandardConnectorRepository.java     | 85 ++++++++++++++++++++++
 .../connector/TestStandardConnectorRepository.java | 70 ++++++++++++++++++
 2 files changed, 155 insertions(+)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
index 0b0382ccfc2..ffd8f2e7a76 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java
@@ -22,6 +22,7 @@ import org.apache.nifi.asset.Asset;
 import org.apache.nifi.asset.AssetManager;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.connector.secrets.SecretsManager;
+import org.apache.nifi.controller.ParameterProviderNode;
 import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.engine.FlowEngine;
 import org.apache.nifi.flow.Bundle;
@@ -190,6 +191,12 @@ public class StandardConnectorRepository implements 
ConnectorRepository {
         // Determine effective name, working config, and ScheduledState
         final ConnectorWorkingConfiguration providerConfig = 
directive.getWorkingConfiguration();
 
+        // Enrich provider-sourced SECRET_REFERENCE values with providerId 
before they are compared
+        // against the in-memory configuration or passed into 
inheritConfiguration.
+        if (providerConfig != null) {
+            
resolveSecretReferencesFromProvider(providerConfig.getWorkingFlowConfiguration());
+        }
+
         final String effectiveName = (providerConfig != null && 
providerConfig.getName() != null)
                 ? providerConfig.getName()
                 : versionedConnector.getName();
@@ -801,6 +808,10 @@ public class StandardConnectorRepository implements 
ConnectorRepository {
 
         final List<VersionedConfigurationStep> workingFlowConfiguration = 
config.getWorkingFlowConfiguration();
         if (workingFlowConfiguration != null) {
+            // Enrich provider-sourced SECRET_REFERENCE values with providerId 
before they are
+            // converted into the in-memory ConnectorValueReference graph.
+            resolveSecretReferencesFromProvider(workingFlowConfiguration);
+
             final MutableConnectorConfigurationContext workingConfigContext = 
connector.getWorkingFlowContext().getConfigurationContext();
             for (final VersionedConfigurationStep step : 
workingFlowConfiguration) {
                 final StepConfiguration stepConfiguration = 
toStepConfiguration(step);
@@ -929,4 +940,78 @@ public class StandardConnectorRepository implements 
ConnectorRepository {
                 versionedReference.getSecretName(), 
versionedReference.getFullyQualifiedSecretName());
         };
     }
+
+    /**
+     * Resolves the {@code providerId} on SECRET_REFERENCE entries that arrive 
from a
+     * {@link ConnectorConfigurationProvider} with only {@code providerName} 
populated. Provider
+     * implementations may legitimately omit {@code providerId} because it is 
a runtime-assigned
+     * UUID; the framework alone has the {@link FlowManager} state needed to 
map a provider name
+     * back to its identifier. Other fields (such as {@code secretName}) are 
the responsibility of
+     * the provider and pass through unchanged.
+     *
+     * <p>This is invoked at the two boundaries where provider-sourced 
configuration enters the
+     * framework (the working config returned by {@link 
ConnectorConfigurationProvider#load(String)}
+     * and the working config returned by {@link 
ConnectorConfigurationProvider#getSyncDirective}).</p>
+     *
+     * <p>Mutates the supplied references in place. References that already 
have a non-null
+     * {@code providerId} are left alone. References whose {@code 
providerName} cannot be
+     * unambiguously resolved are also left with a {@code null} {@code 
providerId} so that the UI
+     * can surface the SECRET_REFERENCE as invalid; see {@link 
#findProviderIdByName(String)}.</p>
+     *
+     * @param steps the configuration steps loaded from the provider; may be 
{@code null} or empty
+     */
+    private void resolveSecretReferencesFromProvider(final 
List<VersionedConfigurationStep> steps) {
+        if (steps == null) {
+            return;
+        }
+        logger.debug("Resolving SECRET_REFERENCE providerIds across {} 
configuration step(s)", steps.size());
+        steps.stream()
+                .filter(Objects::nonNull)
+                .map(VersionedConfigurationStep::getProperties)
+                .filter(Objects::nonNull)
+                .flatMap(properties -> properties.values().stream())
+                .filter(Objects::nonNull)
+                .filter(ref -> 
ConnectorValueType.SECRET_REFERENCE.name().equals(ref.getValueType()))
+                .filter(ref -> ref.getProviderId() == null)
+                .filter(ref -> ref.getProviderName() != null)
+                .forEach(ref -> 
ref.setProviderId(findProviderIdByName(ref.getProviderName())));
+    }
+
+    /**
+     * Resolves a parameter provider id by name, but only when the name 
unambiguously identifies a
+     * single provider in the current flow. If zero or more than one parameter 
provider matches the
+     * given name, returns {@code null} so that the SECRET_REFERENCE will be 
surfaced as invalid in
+     * the UI and the user can re-configure it explicitly.
+     *
+     * <p>Package-private for testing. Production callers should access this 
method via
+     * {@link #resolveSecretReferencesFromProvider}.</p>
+     *
+     * @param providerName the parameter provider name to resolve; may be 
{@code null}
+     * @return the matching parameter provider identifier when exactly one 
provider has the given
+     *         name; {@code null} when {@code providerName} is {@code null}, 
no provider matches,
+     *         or multiple providers share the name
+     */
+    String findProviderIdByName(final String providerName) {
+        if (flowManager == null || providerName == null) {
+            return null;
+        }
+        final List<String> matches = new ArrayList<>();
+        for (final ParameterProviderNode parameterProviderNode : 
flowManager.getAllParameterProviders()) {
+            if (providerName.equals(parameterProviderNode.getName())) {
+                matches.add(parameterProviderNode.getIdentifier());
+            }
+        }
+        if (matches.isEmpty()) {
+            logger.warn("No parameter provider found with name [{}]; 
SECRET_REFERENCE providerId will remain null", providerName);
+            return null;
+        }
+        if (matches.size() > 1) {
+            logger.warn("Multiple ({}) parameter providers found with name 
[{}] (ids={}); SECRET_REFERENCE providerId cannot be unambiguously resolved and 
will remain null",
+                    matches.size(), providerName, matches);
+            return null;
+        }
+        final String resolvedId = matches.getFirst();
+        logger.debug("Resolved parameter provider name [{}] to id [{}]", 
providerName, resolvedId);
+        return resolvedId;
+    }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
index f6e50200852..0bcfd2736b2 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java
@@ -19,6 +19,7 @@ package org.apache.nifi.components.connector;
 
 import org.apache.nifi.asset.Asset;
 import org.apache.nifi.asset.AssetManager;
+import org.apache.nifi.controller.ParameterProviderNode;
 import org.apache.nifi.controller.flow.FlowManager;
 import org.apache.nifi.flow.Bundle;
 import org.apache.nifi.flow.ScheduledState;
@@ -1170,6 +1171,75 @@ public class TestStandardConnectorRepository {
         verify(connector).inheritConfiguration(any(), any(), any());
     }
 
+    // --- findProviderIdByName ---
+
+    @Test
+    public void testFindProviderIdByNameResolvesUniqueMatch() {
+        final ParameterProviderNode snowflake = 
mockParameterProvider("Snowflake Provider", "id-1");
+        final ParameterProviderNode vault = mockParameterProvider("Vault 
Provider", "id-2");
+        final FlowManager flowManager = mock(FlowManager.class);
+        
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake, 
vault));
+
+        final StandardConnectorRepository repository = 
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class), 
flowManager);
+
+        assertEquals("id-1", repository.findProviderIdByName("Snowflake 
Provider"));
+        assertEquals("id-2", repository.findProviderIdByName("Vault 
Provider"));
+    }
+
+    @Test
+    public void testFindProviderIdByNameReturnsNullWhenNoMatch() {
+        final ParameterProviderNode snowflake = 
mockParameterProvider("Snowflake Provider", "id-1");
+        final FlowManager flowManager = mock(FlowManager.class);
+        
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake));
+
+        final StandardConnectorRepository repository = 
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class), 
flowManager);
+
+        assertNull(repository.findProviderIdByName("Nonexistent Provider"));
+    }
+
+    @Test
+    public void testFindProviderIdByNameReturnsNullWhenAmbiguousMatch() {
+        // Two parameter providers share the same name; the resolution must be 
refused so that the
+        // resulting SECRET_REFERENCE is surfaced as invalid in the UI rather 
than silently bound
+        // to whichever provider happens to come first.
+        final ParameterProviderNode dup1 = mockParameterProvider("Duplicate", 
"id-a");
+        final ParameterProviderNode dup2 = mockParameterProvider("Duplicate", 
"id-b");
+        final FlowManager flowManager = mock(FlowManager.class);
+        when(flowManager.getAllParameterProviders()).thenReturn(Set.of(dup1, 
dup2));
+
+        final StandardConnectorRepository repository = 
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class), 
flowManager);
+
+        assertNull(repository.findProviderIdByName("Duplicate"));
+    }
+
+    @Test
+    public void testFindProviderIdByNameReturnsNullForNullInput() {
+        final ParameterProviderNode snowflake = 
mockParameterProvider("Snowflake Provider", "id-1");
+        final FlowManager flowManager = mock(FlowManager.class);
+        
when(flowManager.getAllParameterProviders()).thenReturn(Set.of(snowflake));
+
+        final StandardConnectorRepository repository = 
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class), 
flowManager);
+
+        assertNull(repository.findProviderIdByName(null));
+    }
+
+    @Test
+    public void testFindProviderIdByNameReturnsNullWhenNoProvidersRegistered() 
{
+        final FlowManager flowManager = mock(FlowManager.class);
+        when(flowManager.getAllParameterProviders()).thenReturn(Set.of());
+
+        final StandardConnectorRepository repository = 
createRepositoryWithFlowManager(mock(ConnectorConfigurationProvider.class), 
flowManager);
+
+        assertNull(repository.findProviderIdByName("Anything"));
+    }
+
+    private static ParameterProviderNode mockParameterProvider(final String 
name, final String id) {
+        final ParameterProviderNode node = mock(ParameterProviderNode.class);
+        when(node.getName()).thenReturn(name);
+        when(node.getIdentifier()).thenReturn(id);
+        return node;
+    }
+
     // --- Helper Methods ---
 
     private StandardConnectorRepository 
createRepositoryWithProviderAndAssetManager(

Reply via email to