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

bbende pushed a commit to branch NIFI-15258
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/NIFI-15258 by this push:
     new 3e395f80a3 NIFI-15557: Allow Connectors to enable a Controller Service 
using overridden property values (#10862)
3e395f80a3 is described below

commit 3e395f80a35945d4ca1d1f6db65b3f59f958d0ba
Author: Mark Payne <[email protected]>
AuthorDate: Thu Feb 5 12:44:30 2026 -0500

    NIFI-15557: Allow Connectors to enable a Controller Service using 
overridden property values (#10862)
---
 .../service/StandardControllerServiceNode.java     | 31 +++++++--
 .../apache/nifi/controller/ProcessScheduler.java   | 11 +++
 .../controller/service/ControllerServiceNode.java  | 14 ++++
 .../StandaloneControllerServiceFacade.java         |  2 +-
 .../StandaloneControllerServiceLifecycle.java      | 17 ++++-
 .../scheduling/StandardProcessScheduler.java       | 16 ++++-
 .../TestSocketLoadBalancedFlowFileQueue.java       |  6 +-
 .../TestWriteAheadFlowFileRepository.java          |  2 +-
 .../scheduling/TestStandardProcessScheduler.java   | 81 ++++++++++++++++++++++
 .../AuthorizingControllerServiceLifecycle.java     |  7 ++
 .../scheduling/StatelessProcessScheduler.java      |  6 ++
 11 files changed, 181 insertions(+), 12 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index 6bcc7a2c4d..9cb3876678 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -631,6 +631,13 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
      */
     @Override
     public CompletableFuture<Void> enable(final ScheduledExecutorService 
scheduler, final long administrativeYieldMillis, final boolean 
completeExceptionallyOnFailure) {
+        return enable(scheduler, administrativeYieldMillis, 
completeExceptionallyOnFailure, null);
+    }
+
+    @Override
+    public CompletableFuture<Void> enable(final ScheduledExecutorService 
scheduler, final long administrativeYieldMillis, final boolean 
completeExceptionallyOnFailure,
+            final ConfigurationContext providedConfigurationContext) {
+
         final CompletableFuture<Void> future = new CompletableFuture<>();
 
         if 
(!stateTransition.transitionToEnabling(ControllerServiceState.DISABLED, 
future)) {
@@ -649,7 +656,9 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
         scheduler.execute(new Runnable() {
             @Override
             public void run() {
-                final ConfigurationContext configContext = new 
StandardConfigurationContext(serviceNode, controllerServiceProvider, null);
+                final ConfigurationContext configContext = 
providedConfigurationContext == null
+                    ? new StandardConfigurationContext(serviceNode, 
controllerServiceProvider, null)
+                    : providedConfigurationContext;
 
                 if (!isActive()) {
                     LOG.warn("Enabling {} stopped: no active status", 
serviceNode);
@@ -658,9 +667,17 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
                     return;
                 }
 
-                // Perform Validation and evaluate status before continuing
-                performValidation();
-                final ValidationState validationState = getValidationState();
+                // Perform validation - if a ConfigurationContext was 
provided, validate against its properties
+                final ValidationState validationState;
+                if (providedConfigurationContext == null) {
+                    performValidation();
+                    validationState = getValidationState();
+                } else {
+                    final Map<String, String> properties = 
providedConfigurationContext.getAllProperties();
+                    final ValidationContext validationContext = 
createValidationContext(properties, getAnnotationData(), getParameterLookup(), 
true);
+                    validationState = performValidation(validationContext);
+                }
+
                 final ValidationStatus validationStatus = 
validationState.getStatus();
                 if (validationStatus == ValidationStatus.VALID) {
                     LOG.debug("Enabling {} proceeding after performing 
validation", serviceNode);
@@ -761,6 +778,12 @@ public class StandardControllerServiceNode extends 
AbstractComponentNode impleme
         }
 
         final CompletableFuture<Void> future = new CompletableFuture<>();
+        // If already disabled, complete immediately
+        if (getState() == ControllerServiceState.DISABLED) {
+            future.complete(null);
+            return future;
+        }
+
         final boolean transitioned = 
this.stateTransition.transitionToDisabling(ControllerServiceState.ENABLING, 
future);
         if (transitioned) {
             // If we transitioned from ENABLING to DISABLING, we need to 
immediately complete the disable
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
index 94ca3da1b1..ebef0a18c2 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessScheduler.java
@@ -252,6 +252,17 @@ public interface ProcessScheduler {
      */
     CompletableFuture<Void> enableControllerService(ControllerServiceNode 
service);
 
+    /**
+     * Enables the Controller Service using the provided ConfigurationContext 
instead of deriving
+     * the context from the service's current configuration. This allows 
enabling a service with
+     * temporary/override property values.
+     *
+     * @param service the controller service to enable
+     * @param configurationContext the configuration context to use when 
enabling the service
+     * @return a CompletableFuture that completes when the service has been 
enabled
+     */
+    CompletableFuture<Void> enableControllerService(ControllerServiceNode 
service, ConfigurationContext configurationContext);
+
     /**
      * Disables all of the given Controller Services in the order provided by 
the List
      * @param services the controller services to disable
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
index 16b8b7e8d3..1cd8ede66f 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
@@ -114,6 +114,20 @@ public interface ControllerServiceNode extends 
ComponentNode, VersionedComponent
      */
     CompletableFuture<Void> enable(ScheduledExecutorService scheduler, long 
administrativeYieldMillis, boolean completeExceptionallyOnFailure);
 
+    /**
+     * Enables this service using the provided ConfigurationContext, calling 
any method annotated with @OnEnabled and updating the state of the service.
+     * This allows enabling a service with temporary/override property values 
without modifying the service's configuration.
+     *
+     * @param scheduler implementation of {@link ScheduledExecutorService} 
used to initiate service enabling task as well as its retries
+     * @param administrativeYieldMillis the amount of milliseconds to wait for 
administrative yield
+     * @param completeExceptionallyOnFailure if the Controller Service cannot 
be enabled because it is invalid or throws an Exception from the @OnEnabled 
lifecycle method,
+     *                                       dictates whether the 
CompletableFuture should be completed exceptionally or not
+     * @param configurationContext the ConfigurationContext to use when 
enabling the service, or null to use the default
+     *
+     * @return a CompletableFuture that can be used to wait for the service to 
finish enabling
+     */
+    CompletableFuture<Void> enable(ScheduledExecutorService scheduler, long 
administrativeYieldMillis, boolean completeExceptionallyOnFailure, 
ConfigurationContext configurationContext);
+
     /**
      * Will disable this service. Disabling of the service typically means
      * invoking it's operation that is annotated with @OnDisabled.
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
index 59cbb92516..ff507e4fab 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceFacade.java
@@ -73,7 +73,7 @@ public class StandaloneControllerServiceFacade implements 
ControllerServiceFacad
         this.extensionManager = extensionManager;
         this.assetManager = assetManager;
 
-        this.lifecycle = new 
StandaloneControllerServiceLifecycle(controllerServiceNode, processScheduler);
+        this.lifecycle = new 
StandaloneControllerServiceLifecycle(controllerServiceNode, processScheduler, 
componentContextProvider, parameterContext);
     }
 
     @Override
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceLifecycle.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceLifecycle.java
index de16abd8c4..c8016ce67a 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceLifecycle.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/facades/standalone/StandaloneControllerServiceLifecycle.java
@@ -20,18 +20,27 @@ package 
org.apache.nifi.components.connector.facades.standalone;
 import 
org.apache.nifi.components.connector.components.ControllerServiceLifecycle;
 import org.apache.nifi.components.connector.components.ControllerServiceState;
 import org.apache.nifi.components.validation.ValidationStatus;
+import org.apache.nifi.controller.ConfigurationContext;
 import org.apache.nifi.controller.ProcessScheduler;
 import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.parameter.ParameterContext;
 
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 public class StandaloneControllerServiceLifecycle implements 
ControllerServiceLifecycle {
     private final ControllerServiceNode controllerServiceNode;
     private final ProcessScheduler processScheduler;
+    private final ComponentContextProvider componentContextProvider;
+    private final ParameterContext parameterContext;
+
+    public StandaloneControllerServiceLifecycle(final ControllerServiceNode 
controllerServiceNode, final ProcessScheduler scheduler,
+            final ComponentContextProvider componentContextProvider, final 
ParameterContext parameterContext) {
 
-    public StandaloneControllerServiceLifecycle(final ControllerServiceNode 
controllerServiceNode, final ProcessScheduler scheduler) {
         this.controllerServiceNode = controllerServiceNode;
         this.processScheduler = scheduler;
+        this.componentContextProvider = componentContextProvider;
+        this.parameterContext = parameterContext;
     }
 
     @Override
@@ -55,6 +64,12 @@ public class StandaloneControllerServiceLifecycle implements 
ControllerServiceLi
         return processScheduler.enableControllerService(controllerServiceNode);
     }
 
+    @Override
+    public CompletableFuture<Void> enable(final Map<String, String> 
propertyValueOverrides) {
+        final ConfigurationContext configurationContext = 
componentContextProvider.createConfigurationContext(controllerServiceNode, 
propertyValueOverrides, parameterContext);
+        return processScheduler.enableControllerService(controllerServiceNode, 
configurationContext);
+    }
+
     @Override
     public CompletableFuture<Void> disable() {
         return 
processScheduler.disableControllerService(controllerServiceNode);
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
index 740498adb6..4c30f67312 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/scheduling/StandardProcessScheduler.java
@@ -882,7 +882,18 @@ public final class StandardProcessScheduler implements 
ProcessScheduler {
         return enableControllerService(service, true);
     }
 
+    @Override
+    public CompletableFuture<Void> enableControllerService(final 
ControllerServiceNode service, final ConfigurationContext configurationContext) 
{
+        return enableControllerService(service, true, configurationContext);
+    }
+
     private CompletableFuture<Void> enableControllerService(final 
ControllerServiceNode service, final boolean completeFutureExceptionally) {
+        return enableControllerService(service, completeFutureExceptionally, 
null);
+    }
+
+    private CompletableFuture<Void> enableControllerService(final 
ControllerServiceNode service, final boolean completeFutureExceptionally,
+            final ConfigurationContext configurationContext) {
+
         if (service.isActive()) {
             LOG.debug("{} is already active, so not enabling it again", 
service);
             return CompletableFuture.completedFuture(null);
@@ -897,10 +908,11 @@ public final class StandardProcessScheduler implements 
ProcessScheduler {
         for (final ControllerServiceNode dependentService : dependentServices) 
{
             // Enable Controller Service but if it fails, do not complete the 
future Exceptionally. This allows us to wait up until the
             // timeout for the service to enable, even if it needs to retry in 
order to do so.
-            futures.add(enableControllerService(dependentService, 
completeFutureExceptionally));
+            // Note: dependent services always use their own configuration, 
not the provided configurationContext
+            futures.add(enableControllerService(dependentService, 
completeFutureExceptionally, null));
         }
 
-        futures.add(service.enable(this.componentLifeCycleThreadPool, 
this.administrativeYieldMillis, completeFutureExceptionally));
+        futures.add(service.enable(this.componentLifeCycleThreadPool, 
this.administrativeYieldMillis, completeFutureExceptionally, 
configurationContext));
         return CompletableFuture.allOf(futures.toArray(new 
CompletableFuture[0]));
     }
 
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSocketLoadBalancedFlowFileQueue.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSocketLoadBalancedFlowFileQueue.java
index d4f0ea83fe..21fd885a03 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSocketLoadBalancedFlowFileQueue.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/TestSocketLoadBalancedFlowFileQueue.java
@@ -21,6 +21,7 @@ import 
org.apache.nifi.cluster.coordination.ClusterCoordinator;
 import org.apache.nifi.cluster.coordination.ClusterTopologyEventListener;
 import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
 import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.connector.DropFlowFileSummary;
 import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.controller.MockFlowFileRecord;
 import org.apache.nifi.controller.MockSwapManager;
@@ -30,7 +31,6 @@ import 
org.apache.nifi.controller.queue.clustered.client.async.AsyncLoadBalanceC
 import 
org.apache.nifi.controller.queue.clustered.partition.FlowFilePartitioner;
 import org.apache.nifi.controller.queue.clustered.partition.QueuePartition;
 import 
org.apache.nifi.controller.queue.clustered.partition.RoundRobinPartitioner;
-import org.apache.nifi.components.connector.DropFlowFileSummary;
 import org.apache.nifi.controller.repository.ContentRepository;
 import org.apache.nifi.controller.repository.FlowFileRecord;
 import org.apache.nifi.controller.repository.FlowFileRepository;
@@ -39,11 +39,11 @@ import 
org.apache.nifi.controller.repository.RepositoryRecordType;
 import org.apache.nifi.controller.repository.SwapSummary;
 import org.apache.nifi.controller.status.FlowFileAvailability;
 import org.apache.nifi.events.EventReporter;
+import org.apache.nifi.flowfile.FlowFilePrioritizer;
 import org.apache.nifi.provenance.ProvenanceEventRecord;
+import org.apache.nifi.provenance.ProvenanceEventRepository;
 import org.apache.nifi.provenance.ProvenanceEventType;
 import org.apache.nifi.provenance.StandardProvenanceEventRecord;
-import org.apache.nifi.flowfile.FlowFilePrioritizer;
-import org.apache.nifi.provenance.ProvenanceEventRepository;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
index 9444cd1a6c..3d79cf4fa5 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java
@@ -20,7 +20,6 @@ import 
org.apache.nifi.components.connector.DropFlowFileSummary;
 import org.apache.nifi.connectable.Connectable;
 import org.apache.nifi.connectable.Connection;
 import org.apache.nifi.controller.MockFlowFileRecord;
-import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.controller.queue.DropFlowFileStatus;
 import org.apache.nifi.controller.queue.FlowFileQueue;
 import org.apache.nifi.controller.queue.FlowFileQueueSize;
@@ -41,6 +40,7 @@ import 
org.apache.nifi.controller.repository.claim.StandardResourceClaimManager;
 import org.apache.nifi.controller.status.FlowFileAvailability;
 import org.apache.nifi.controller.swap.StandardSwapContents;
 import org.apache.nifi.controller.swap.StandardSwapSummary;
+import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.flowfile.FlowFilePrioritizer;
 import org.apache.nifi.processor.FlowFileFilter;
 import org.apache.nifi.util.MockFlowFile;
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
index 3b154d1a59..18cd327d01 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/scheduling/TestStandardProcessScheduler.java
@@ -47,6 +47,7 @@ import 
org.apache.nifi.controller.scheduling.processors.FailOnScheduledProcessor
 import org.apache.nifi.controller.service.ControllerServiceNode;
 import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.controller.service.StandardConfigurationContext;
 import org.apache.nifi.controller.service.StandardControllerServiceProvider;
 import org.apache.nifi.controller.service.mock.MockProcessGroup;
 import org.apache.nifi.engine.FlowEngine;
@@ -63,6 +64,7 @@ import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.StandardProcessorInitializationContext;
 import org.apache.nifi.processor.StandardValidationContextFactory;
 import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.reporting.AbstractReportingTask;
 import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.reporting.ReportingContext;
@@ -87,6 +89,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
@@ -94,6 +97,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -488,6 +492,51 @@ public class TestStandardProcessScheduler {
         assertEquals(0, ts.disableInvocationCount());
     }
 
+    @Test
+    @Timeout(10)
+    public void testEnableControllerServiceWithConfigurationContext() throws 
Exception {
+        final ControllerServiceNode serviceNode = 
flowManager.createControllerService(PropertyTrackingService.class.getName(),
+            "property-tracking-service", 
systemBundle.getBundleDetails().getCoordinate(), null, false, true, null);
+
+        rootGroup.addControllerService(serviceNode);
+        
serviceNode.setProperties(Map.of(PropertyTrackingService.TRACKING_PROPERTY.getName(),
 "original-value"));
+        serviceNode.performValidation();
+
+        final ConfigurationContext overrideContext = new 
StandardConfigurationContext(
+            serviceNode, 
Map.of(PropertyTrackingService.TRACKING_PROPERTY.getName(), 
"overridden-value"), null,
+            rootGroup.getParameterContext(), serviceProvider, null);
+
+        final CompletableFuture<Void> future = 
scheduler.enableControllerService(serviceNode, overrideContext);
+        future.get(5, TimeUnit.SECONDS);
+
+        final PropertyTrackingService service = (PropertyTrackingService) 
serviceNode.getControllerServiceImplementation();
+        assertEquals(1, service.enableInvocationCount());
+        assertEquals("overridden-value", service.getEnabledPropertyValue());
+        assertEquals(ControllerServiceState.ENABLED, serviceNode.getState());
+    }
+
+    @Test
+    @Timeout(10)
+    public void 
testEnableControllerServiceWithConfigurationContextUsesOverriddenProperties() 
throws ExecutionException, InterruptedException, TimeoutException {
+        final ControllerServiceNode serviceNode = 
flowManager.createControllerService(PropertyTrackingService.class.getName(),
+            "property-tracking-service-2", 
systemBundle.getBundleDetails().getCoordinate(), null, false, true, null);
+
+        rootGroup.addControllerService(serviceNode);
+        serviceNode.performValidation();
+
+        final ConfigurationContext validOverrideContext = new 
StandardConfigurationContext(
+            serviceNode, 
Map.of(PropertyTrackingService.TRACKING_PROPERTY.getName(), "override-value"), 
null,
+            rootGroup.getParameterContext(), serviceProvider, null);
+
+        final CompletableFuture<Void> future = 
scheduler.enableControllerService(serviceNode, validOverrideContext);
+        future.get(5, TimeUnit.SECONDS);
+
+        final PropertyTrackingService service = (PropertyTrackingService) 
serviceNode.getControllerServiceImplementation();
+        assertEquals(1, service.enableInvocationCount());
+        assertEquals("override-value", service.getEnabledPropertyValue());
+        assertEquals(ControllerServiceState.ENABLED, serviceNode.getState());
+    }
+
     // Test that if processor throws Exception in @OnScheduled, it keeps 
getting scheduled
     @Test
     @Timeout(10)
@@ -619,6 +668,38 @@ public class TestStandardProcessScheduler {
         }
     }
 
+    public static class PropertyTrackingService extends 
AbstractControllerService {
+        public static final PropertyDescriptor TRACKING_PROPERTY = new 
PropertyDescriptor.Builder()
+            .name("Tracking Property")
+            .description("A property for tracking what value was used during 
enabling")
+            .required(false)
+            .defaultValue("default-value")
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .build();
+
+        private volatile String enabledPropertyValue;
+        private final AtomicInteger enableCounter = new AtomicInteger();
+
+        @Override
+        protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+            return List.of(TRACKING_PROPERTY);
+        }
+
+        @OnEnabled
+        public void enable(final ConfigurationContext context) {
+            this.enabledPropertyValue = 
context.getProperty(TRACKING_PROPERTY).getValue();
+            this.enableCounter.incrementAndGet();
+        }
+
+        public String getEnabledPropertyValue() {
+            return enabledPropertyValue;
+        }
+
+        public int enableInvocationCount() {
+            return enableCounter.get();
+        }
+    }
+
     private StandardProcessScheduler createScheduler() {
         return new StandardProcessScheduler(new FlowEngine(1, "Unit Test", 
true), Mockito.mock(FlowController.class),
             stateMgrProvider, nifiProperties, new 
StandardLifecycleStateManager());
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
index 838632fdfb..9c4332a45a 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/connector/authorization/AuthorizingControllerServiceLifecycle.java
@@ -19,6 +19,7 @@ package org.apache.nifi.web.connector.authorization;
 import 
org.apache.nifi.components.connector.components.ControllerServiceLifecycle;
 import org.apache.nifi.components.connector.components.ControllerServiceState;
 
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -47,6 +48,12 @@ public class AuthorizingControllerServiceLifecycle 
implements ControllerServiceL
         return delegate.enable();
     }
 
+    @Override
+    public CompletableFuture<Void> enable(final Map<String, String> 
propertyValueOverrides) {
+        authContext.authorizeWrite();
+        return delegate.enable(propertyValueOverrides);
+    }
+
     @Override
     public CompletableFuture<Void> disable() {
         authContext.authorizeWrite();
diff --git 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
index 72c026083b..802bbcb1ed 100644
--- 
a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
+++ 
b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/controller/scheduling/StatelessProcessScheduler.java
@@ -329,6 +329,12 @@ public class StatelessProcessScheduler implements 
ProcessScheduler {
         return service.enable(componentLifeCycleThreadPool, 
ADMINISTRATIVE_YIELD_MILLIS, true);
     }
 
+    @Override
+    public CompletableFuture<Void> enableControllerService(final 
ControllerServiceNode service, final ConfigurationContext configurationContext) 
{
+        logger.info("Enabling {}", service);
+        return service.enable(componentLifeCycleThreadPool, 
ADMINISTRATIVE_YIELD_MILLIS, true, configurationContext);
+    }
+
     @Override
     public CompletableFuture<Void> disableControllerService(final 
ControllerServiceNode service) {
         logger.info("Disabling {}", service);

Reply via email to