You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2019/08/16 20:48:39 UTC

[nifi] 01/02: NIFI-6382: Allow Parameters to have null values

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

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

commit bcf373a049dd8d393b8cc3c36e2a26950d07a636
Author: Mark Payne <ma...@hotmail.com>
AuthorDate: Thu Aug 8 10:54:03 2019 -0400

    NIFI-6382: Allow Parameters to have null values
    
    Signed-off-by: Bryan Bende <bb...@apache.org>
---
 .../apache/nifi/components/ValidationContext.java  |   8 ++
 .../NotificationValidationContext.java             |   5 +
 .../apache/nifi/util/MockValidationContext.java    |   8 +-
 .../org/apache/nifi/util/db/TestJdbcCommon.java    |  54 +++++-----
 .../nifi/controller/AbstractComponentNode.java     |   8 ++
 .../apache/nifi/controller/flow/FlowManager.java   |   3 +-
 .../apache/nifi/parameter/ParameterContext.java    |  14 +--
 .../nifi/controller/StandardFlowSynchronizer.java  |   5 +-
 .../nifi/controller/flow/StandardFlowManager.java  |   2 +-
 .../nifi/parameter/StandardParameterContext.java   |  83 +++++++--------
 .../nifi/processor/StandardValidationContext.java  |  17 +++
 .../nifi/controller/TestStandardProcessorNode.java |   5 +
 .../nifi/integration/auth/AlwaysAuthenticate.java  |  46 ++++++++
 .../auth/VolatileAccessPolicyProvider.java         | 118 +++++++++++++++++++++
 .../auth/VolatileUserGroupProvider.java            | 111 +++++++++++++++++++
 .../nifi/integration/parameters/ParametersIT.java  |  20 ++--
 .../processor/ProcessorParameterTokenIT.java       |  13 ++-
 .../nifi/integration/versioned/ImportFlowIT.java   |  22 +++-
 .../parameter/TestStandardParameterContext.java    |  63 ++++++-----
 .../resources/int-tests/clustered-nifi.properties  |   2 +-
 .../apache/nifi/web/StandardNiFiServiceFacade.java |   9 +-
 .../web/dao/impl/StandardParameterContextDAO.java  |  36 +++++--
 .../nifi/script/impl/ValidationContextAdapter.java |   5 +
 .../stateless/core/StatelessParameterContext.java  |   8 +-
 .../stateless/core/StatelessValidationContext.java |  18 ++++
 25 files changed, 530 insertions(+), 153 deletions(-)

diff --git a/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java b/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
index 95a8c09..acaffd7 100644
--- a/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
+++ b/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
@@ -106,4 +106,12 @@ public interface ValidationContext extends PropertyContext {
      * @return <code>true</code> if a Parameter with the given name is defined in the currently selected Parameter Context
      */
     boolean isParameterDefined(String parameterName);
+
+    /**
+     * Returns <code>true</code> if a Parameter with the given name is defined and has a non-null value, <code>false</code> if either the Parameter
+     * is not defined or the Parameter is defined but has a value of <code>null</code>.
+     * @param parameterName the name of the parameter
+     * @return <code>true</code> if the Parameter is defined and has a non-null value, false otherwise
+     */
+    boolean isParameterSet(String parameterName);
 }
diff --git a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/notification/NotificationValidationContext.java b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/notification/NotificationValidationContext.java
index 8f7eda0..0075df2 100644
--- a/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/notification/NotificationValidationContext.java
+++ b/nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/notification/NotificationValidationContext.java
@@ -133,4 +133,9 @@ public class NotificationValidationContext implements ValidationContext {
     public boolean isParameterDefined(final String parameterName) {
         return false;
     }
+
+    @Override
+    public boolean isParameterSet(final String parameterName) {
+        return false;
+    }
 }
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
index 87389be..e913204 100644
--- a/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockValidationContext.java
@@ -181,8 +181,12 @@ public class MockValidationContext extends MockControllerServiceLookup implement
 
     @Override
     public boolean isParameterDefined(final String parameterName) {
-        // TODO: Implement
-        return false;
+        return true;
+    }
+
+    @Override
+    public boolean isParameterSet(final String parameterName) {
+        return true;
     }
 
 }
diff --git a/nifi-nar-bundles/nifi-extension-utils/nifi-database-utils/src/test/java/org/apache/nifi/util/db/TestJdbcCommon.java b/nifi-nar-bundles/nifi-extension-utils/nifi-database-utils/src/test/java/org/apache/nifi/util/db/TestJdbcCommon.java
index b58289b..bbf2bdb 100644
--- a/nifi-nar-bundles/nifi-extension-utils/nifi-database-utils/src/test/java/org/apache/nifi/util/db/TestJdbcCommon.java
+++ b/nifi-nar-bundles/nifi-extension-utils/nifi-database-utils/src/test/java/org/apache/nifi/util/db/TestJdbcCommon.java
@@ -16,13 +16,26 @@
  */
 package org.apache.nifi.util.db;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import org.apache.avro.Conversions;
+import org.apache.avro.LogicalType;
+import org.apache.avro.LogicalTypes;
+import org.apache.avro.Schema;
+import org.apache.avro.file.DataFileStream;
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.generic.GenericRecord;
+import org.apache.avro.io.DatumReader;
+import org.apache.avro.util.Utf8;
+import org.apache.commons.io.input.ReaderInputStream;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -63,26 +76,13 @@ import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.stream.IntStream;
 
-import org.apache.avro.Conversions;
-import org.apache.avro.LogicalType;
-import org.apache.avro.LogicalTypes;
-import org.apache.avro.Schema;
-import org.apache.avro.file.DataFileStream;
-import org.apache.avro.generic.GenericData;
-import org.apache.avro.generic.GenericDatumReader;
-import org.apache.avro.generic.GenericRecord;
-import org.apache.avro.io.DatumReader;
-import org.apache.avro.util.Utf8;
-import org.apache.commons.io.input.ReaderInputStream;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
-import org.mockito.stubbing.Answer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class TestJdbcCommon {
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
index f5752a3..48e359f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
@@ -659,6 +659,14 @@ public abstract class AbstractComponentNode implements ComponentNode {
                         .explanation("Property references Parameter '" + paramName + "' but the currently selected Parameter Context does not have a Parameter with that name")
                         .build());
                 }
+
+                if (!validationContext.isParameterSet(paramName)) {
+                    results.add(new ValidationResult.Builder()
+                        .subject(propertyDescriptor.getDisplayName())
+                        .valid(false)
+                        .explanation("Property references Parameter '" + paramName + "' but the currently selected Parameter Context does not have a value set for that Parameter")
+                        .build());
+                }
             }
         }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
index 4d108e9..d072698 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/flow/FlowManager.java
@@ -36,6 +36,7 @@ import org.apache.nifi.web.api.dto.FlowSnippetDTO;
 
 import java.net.URL;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
@@ -322,7 +323,7 @@ public interface FlowManager {
 
     void removeRootControllerService(final ControllerServiceNode service);
 
-    ParameterContext createParameterContext(String id, String name, Set<Parameter> parameters);
+    ParameterContext createParameterContext(String id, String name, Map<String, Parameter> parameters);
 
     ParameterContextManager getParameterContextManager();
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java
index dae4d9e..fcbd270 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/parameter/ParameterContext.java
@@ -20,7 +20,6 @@ import org.apache.nifi.authorization.resource.Authorizable;
 
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 
 public interface ParameterContext extends ParameterLookup, Authorizable {
 
@@ -52,19 +51,22 @@ public interface ParameterContext extends ParameterLookup, Authorizable {
     void setDescription(String description);
 
     /**
-     * Updates the Parameters within this context to match the given set of Parameters.
-     * @param updatedParameters the updated set of parameters
+     * Updates the Parameters within this context to match the given set of Parameters. If the Parameter Context contains any parameters that are not in
+     * the given set of updated Parameters, those parameters are unaffected. However, if the Map contains any key with a <code>null</code> value, the
+     * parameter whose name is given by the key will be removed
+     *
+     * @param updatedParameters the updated set of parameters, keyed by Parameter name
      * @throws IllegalStateException if any parameter is modified or removed and that parameter is being referenced by a running Processor or an enabled Controller Service, or if
      * an update would result in changing the sensitivity of any parameter
      */
-    void setParameters(Set<Parameter> updatedParameters);
+    void setParameters(Map<String, Parameter> updatedParameters);
 
     /**
      * Ensures that it is legal to update the Parameters for this Parameter Context to match the given set of Parameters
-     * @param parameters the Set of Parameters that are to become the new Parameters for this Parameter Context
+     * @param parameters the updated set of parameters, keyed by Parameter name
      * @throws IllegalStateException if setting the given set of Parameters is not legal
      */
-    void verifyCanSetParameters(Set<Parameter> parameters);
+    void verifyCanSetParameters(Map<String, Parameter> parameters);
 
 
     /**
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
index a2c8d4c..6d970db 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
@@ -131,6 +131,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.zip.GZIPInputStream;
 
@@ -547,10 +548,10 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
     }
 
     private ParameterContext createParameterContext(final ParameterContextDTO dto, final FlowManager flowManager) {
-        final Set<Parameter> parameters = dto.getParameters().stream()
+        final Map<String, Parameter> parameters = dto.getParameters().stream()
             .map(ParameterEntity::getParameter)
             .map(this::createParameter)
-            .collect(Collectors.toSet());
+            .collect(Collectors.toMap(param -> param.getDescriptor().getName(), Function.identity()));
 
         final ParameterContext context = flowManager.createParameterContext(dto.getId(), dto.getName(), parameters);
         context.setDescription(dto.getDescription());
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
index 1ec226e..f2f1bc7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/flow/StandardFlowManager.java
@@ -736,7 +736,7 @@ public class StandardFlowManager implements FlowManager {
     }
 
     @Override
-    public ParameterContext createParameterContext(final String id, final String name, final Set<Parameter> parameters) {
+    public ParameterContext createParameterContext(final String id, final String name, final Map<String, Parameter> parameters) {
         final boolean namingConflict = parameterContextManager.getParameterContexts().stream()
             .anyMatch(paramContext -> paramContext.getName().equals(name));
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
index e5d12a5..04cfb8a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
@@ -34,7 +34,6 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -97,32 +96,26 @@ public class StandardParameterContext implements ParameterContext {
         return description;
     }
 
-    public void setParameters(final Set<Parameter> updatedParameters) {
+    public void setParameters(final Map<String, Parameter> updatedParameters) {
         writeLock.lock();
         try {
             this.version++;
-
             verifyCanSetParameters(updatedParameters);
 
             boolean changeAffectingComponents = false;
-            for (final Parameter parameter : updatedParameters) {
-                if (parameter.getValue() == null && parameter.getDescriptor().getDescription() == null) {
-                    parameters.remove(parameter.getDescriptor());
+            for (final Map.Entry<String, Parameter> entry : updatedParameters.entrySet()) {
+                final String parameterName = entry.getKey();
+                final Parameter parameter = entry.getValue();
+
+                if (parameter == null) {
+                    final ParameterDescriptor parameterDescriptor = new ParameterDescriptor.Builder().name(parameterName).build();
+                    parameters.remove(parameterDescriptor);
                     changeAffectingComponents = true;
-                } else if (parameter.getValue() == null) {
-                    // Value is null but description is not. Just update the description of the existing Parameter.
-                    final Parameter existingParameter = parameters.get(parameter.getDescriptor());
-                    final ParameterDescriptor existingDescriptor = existingParameter.getDescriptor();
-                    final ParameterDescriptor replacementDescriptor = new ParameterDescriptor.Builder()
-                        .from(existingDescriptor)
-                        .description(parameter.getDescriptor().getDescription())
-                        .build();
-
-                    final Parameter replacementParameter = new Parameter(replacementDescriptor, existingParameter.getValue());
-                    parameters.put(parameter.getDescriptor(), replacementParameter);
                 } else {
-                    parameters.put(parameter.getDescriptor(), parameter);
-                    changeAffectingComponents = true;
+                    final Parameter oldParameter = parameters.put(parameter.getDescriptor(), parameter);
+                    if (oldParameter == null || !Objects.equals(oldParameter.getValue(), parameter.getValue())) {
+                        changeAffectingComponents = true;
+                    }
                 }
             }
 
@@ -195,25 +188,23 @@ public class StandardParameterContext implements ParameterContext {
     }
 
     @Override
-    public void verifyCanSetParameters(final Set<Parameter> updatedParameters) {
+    public void verifyCanSetParameters(final Map<String, Parameter> updatedParameters) {
         // Ensure that the updated parameters will not result in changing the sensitivity flag of any parameter.
-        for (final Parameter updatedParameter : updatedParameters) {
-            validateSensitiveFlag(updatedParameter);
-
-            // Parameters' names and sensitivity flags are immutable. However, the description and value are mutable. If both value and description are
-            // set to `null`, this is the indication that the Parameter should be removed. If the value is `null` but the Description is supplied, the user
-            // is indicating that only the description is to be changed.
-            if (updatedParameter.getValue() == null && updatedParameter.getDescriptor().getDescription() == null) {
-                validateReferencingComponents(updatedParameter, "remove");
-            } else if (updatedParameter.getValue() != null) {
-                validateReferencingComponents(updatedParameter, "update");
-            } else {
-                // Only parameter is changing. No value is set. This means that the Parameter must already exist.
-                final Optional<Parameter> existing = getParameter(updatedParameter.getDescriptor());
-                if (!existing.isPresent()) {
-                    throw new IllegalStateException("Cannot add Parameter '" + updatedParameter.getDescriptor().getName() + "' without providing a value");
-                }
+        for (final Map.Entry<String, Parameter> entry : updatedParameters.entrySet()) {
+            final String parameterName = entry.getKey();
+            final Parameter parameter = entry.getValue();
+            if (parameter == null) {
+                // parameter is being deleted.
+                validateReferencingComponents(parameterName, null,"remove");
+                continue;
             }
+
+            if (!Objects.equals(parameterName, parameter.getDescriptor().getName())) {
+                throw new IllegalArgumentException("Parameter '" + parameterName + "' was specified with the wrong key in the Map");
+            }
+
+            validateSensitiveFlag(parameter);
+            validateReferencingComponents(parameterName, parameter, "update");
         }
     }
 
@@ -236,25 +227,27 @@ public class StandardParameterContext implements ParameterContext {
     }
 
 
-    private void validateReferencingComponents(final Parameter updatedParameter, final String parameterAction) {
-        final String paramName = updatedParameter.getDescriptor().getName();
-
-        for (final ProcessorNode procNode : parameterReferenceManager.getProcessorsReferencing(this, paramName)) {
+    private void validateReferencingComponents(final String parameterName, final Parameter parameter, final String parameterAction) {
+        for (final ProcessorNode procNode : parameterReferenceManager.getProcessorsReferencing(this, parameterName)) {
             if (procNode.isRunning()) {
-                throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + paramName + "' because it is referenced by " + procNode + ", which is currently running");
+                throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + parameterName + "' because it is referenced by " + procNode + ", which is currently running");
             }
 
-            validateParameterSensitivity(updatedParameter, procNode);
+            if (parameter != null) {
+                validateParameterSensitivity(parameter, procNode);
+            }
         }
 
-        for (final ControllerServiceNode serviceNode : parameterReferenceManager.getControllerServicesReferencing(this, paramName)) {
+        for (final ControllerServiceNode serviceNode : parameterReferenceManager.getControllerServicesReferencing(this, parameterName)) {
             final ControllerServiceState serviceState = serviceNode.getState();
             if (serviceState != ControllerServiceState.DISABLED) {
-                throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + paramName + "' because it is referenced by "
+                throw new IllegalStateException("Cannot " + parameterAction + " parameter '" + parameterName + "' because it is referenced by "
                     + serviceNode + ", which currently has a state of " + serviceState);
             }
 
-            validateParameterSensitivity(updatedParameter, serviceNode);
+            if (parameter != null) {
+                validateParameterSensitivity(parameter, serviceNode);
+            }
         }
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
index b1a44e7..3fc7e06 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
@@ -33,6 +33,7 @@ import org.apache.nifi.controller.service.ControllerServiceProvider;
 import org.apache.nifi.controller.service.ControllerServiceState;
 import org.apache.nifi.expression.ExpressionLanguageCompiler;
 import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.parameter.Parameter;
 import org.apache.nifi.parameter.ParameterContext;
 import org.apache.nifi.parameter.ParameterReference;
 import org.apache.nifi.registry.VariableRegistry;
@@ -43,6 +44,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -217,6 +219,21 @@ public class StandardValidationContext implements ValidationContext {
     }
 
     @Override
+    public boolean isParameterSet(final String parameterName) {
+        if (parameterContext == null) {
+            return false;
+        }
+
+        final Optional<Parameter> parameterOption = parameterContext.getParameter(parameterName);
+        if (!parameterOption.isPresent()) {
+            return false;
+        }
+
+        final String value = parameterOption.get().getValue();
+        return value != null;
+    }
+
+    @Override
     public String toString() {
         return "StandardValidationContext[componentId=" + componentId + ", properties=" + properties + "]";
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
index 08dc4f3..d1fb511 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java
@@ -550,6 +550,11 @@ public class TestStandardProcessorNode {
                     public boolean isParameterDefined(final String parameterName) {
                         return false;
                     }
+
+                    @Override
+                    public boolean isParameterSet(final String parameterName) {
+                        return false;
+                    }
                 };
             }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/AlwaysAuthenticate.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/AlwaysAuthenticate.java
new file mode 100644
index 0000000..9640be5
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/AlwaysAuthenticate.java
@@ -0,0 +1,46 @@
+/*
+ * 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.integration.auth;
+
+import org.apache.nifi.authentication.AuthenticationResponse;
+import org.apache.nifi.authentication.LoginCredentials;
+import org.apache.nifi.authentication.LoginIdentityProvider;
+import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext;
+import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext;
+import org.apache.nifi.authentication.exception.IdentityAccessException;
+import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
+import org.apache.nifi.authentication.exception.ProviderCreationException;
+import org.apache.nifi.authentication.exception.ProviderDestructionException;
+
+public class AlwaysAuthenticate implements LoginIdentityProvider {
+    @Override
+    public AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException {
+        return new AuthenticationResponse(credentials.getUsername(), credentials.getUsername(), 1_000_000L, "unit-test-issuer");
+    }
+
+    @Override
+    public void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
+    }
+
+    @Override
+    public void onConfigured(final LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException {
+    }
+
+    @Override
+    public void preDestruction() throws ProviderDestructionException {
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileAccessPolicyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileAccessPolicyProvider.java
new file mode 100644
index 0000000..c010f05
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileAccessPolicyProvider.java
@@ -0,0 +1,118 @@
+/*
+ * 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.integration.auth;
+
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.AccessPolicyProvider;
+import org.apache.nifi.authorization.AccessPolicyProviderInitializationContext;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+public class VolatileAccessPolicyProvider implements AccessPolicyProvider {
+    private final VolatileUserGroupProvider userGroupProvider = new VolatileUserGroupProvider();
+    private Set<AccessPolicy> accessPolicies = new HashSet<>();
+
+    public synchronized void grantAccess(final String user, final String resourceIdentifier, final RequestAction action) {
+        final AccessPolicy existingPolicy = getAccessPolicy(resourceIdentifier, action);
+
+        final AccessPolicy policy;
+        if (existingPolicy == null) {
+            policy = new AccessPolicy.Builder()
+                .addUser(user)
+                .action(action)
+                .identifierGenerateRandom()
+                .resource(resourceIdentifier)
+                .build();
+        } else {
+            policy = new AccessPolicy.Builder()
+                .addUsers(existingPolicy.getUsers())
+                .addUser(user)
+                .action(action)
+                .identifier(existingPolicy.getIdentifier())
+                .resource(resourceIdentifier)
+                .build();
+        }
+
+        accessPolicies.remove(existingPolicy);
+        accessPolicies.add(policy);
+    }
+
+    public synchronized void revokeAccess(final String user, final String resourceIdentifier, final RequestAction action) {
+        final AccessPolicy existingPolicy = getAccessPolicy(resourceIdentifier, action);
+
+        if (existingPolicy == null) {
+            return;
+        }
+
+        final AccessPolicy policy= new AccessPolicy.Builder()
+                .addUsers(existingPolicy.getUsers())
+                .removeUser(user)
+                .action(action)
+                .identifier(existingPolicy.getIdentifier())
+                .resource(resourceIdentifier)
+                .build();
+
+        accessPolicies.remove(existingPolicy);
+        accessPolicies.add(policy);
+    }
+
+    @Override
+    public synchronized Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+        return new HashSet<>(accessPolicies);
+    }
+
+    @Override
+    public synchronized AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
+        return accessPolicies.stream()
+            .filter(policy -> policy.getIdentifier().equals(identifier))
+            .findAny()
+            .orElse(null);
+    }
+
+    @Override
+    public synchronized AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) throws AuthorizationAccessException {
+        return accessPolicies.stream()
+            .filter(policy -> Objects.equals(policy.getResource(), resourceIdentifier))
+            .filter(policy -> Objects.equals(policy.getAction(), action))
+            .findAny()
+            .orElse(null);
+    }
+
+    @Override
+    public synchronized VolatileUserGroupProvider getUserGroupProvider() {
+        return userGroupProvider;
+    }
+
+    @Override
+    public void initialize(final AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileUserGroupProvider.java
new file mode 100644
index 0000000..7e4849b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/auth/VolatileUserGroupProvider.java
@@ -0,0 +1,111 @@
+/*
+ * 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.integration.auth;
+
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.UserAndGroups;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class VolatileUserGroupProvider implements UserGroupProvider {
+    private final Map<String, User> users = new HashMap<>();
+    private final Map<String, Group> groups = new HashMap<>();
+    private final Map<User, Set<Group>> userGroupMapping = new HashMap<>();
+
+    public void addUser(final User user) {
+        this.users.put(user.getIdentifier(), user);
+    }
+
+    public void addGroup(final Group group) {
+        this.groups.put(group.getIdentifier(), group);
+    }
+
+    public void addUserToGroup(final User user, final Group group) {
+        userGroupMapping.computeIfAbsent(user, i -> new HashSet<>()).add(group);
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return new HashSet<>(users.values());
+    }
+
+    @Override
+    public User getUser(final String identifier) throws AuthorizationAccessException {
+        return users.get(identifier);
+    }
+
+    @Override
+    public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
+        return users.values().stream()
+            .filter(user -> Objects.equals(identity, user.getIdentity()))
+            .findFirst()
+            .orElse(null);
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return new HashSet<>(groups.values());
+    }
+
+    @Override
+    public Group getGroup(final String identifier) throws AuthorizationAccessException {
+        return groups.get(identifier);
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
+        final User user = getUserByIdentity(identity);
+        final Set<Group> groups = userGroupMapping.get(user);
+        final Set<Group> groupCopy = groups == null ? Collections.emptySet() : new HashSet<>(groups);
+
+        return new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return groupCopy;
+            }
+        };
+    }
+
+    @Override
+    public void initialize(final UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/parameters/ParametersIT.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/parameters/ParametersIT.java
index 7d0755d..4a394ae 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/parameters/ParametersIT.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/parameters/ParametersIT.java
@@ -58,7 +58,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
         updateAttribute.setProperties(Collections.singletonMap("test", "#{test}"));
@@ -83,7 +83,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
         updateAttribute.setProperties(Collections.singletonMap("test", "${#{test}:toUpper()}"));
@@ -108,7 +108,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
         updateAttribute.setProperties(Collections.singletonMap("test", "${#{test}:toUpper()}"));
@@ -133,7 +133,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -165,7 +165,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -193,7 +193,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -225,7 +225,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -267,7 +267,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -307,7 +307,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
@@ -337,7 +337,7 @@ public class ParametersIT extends FrameworkIntegrationTest {
 
         final ParameterReferenceManager referenceManager = new StandardParameterReferenceManager(getFlowController().getFlowManager());
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "param-context", referenceManager, null);
-        parameterContext.setParameters(Collections.singleton(new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
+        parameterContext.setParameters(Collections.singletonMap("test", new Parameter(new ParameterDescriptor.Builder().name("test").build(), "unit")));
 
         getRootGroup().setParameterContext(parameterContext);
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/processor/ProcessorParameterTokenIT.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/processor/ProcessorParameterTokenIT.java
index 3843d2a..906b092 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/processor/ProcessorParameterTokenIT.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/processor/ProcessorParameterTokenIT.java
@@ -40,7 +40,6 @@ import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,9 +76,9 @@ public class ProcessorParameterTokenIT extends FrameworkIntegrationTest {
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "testEscapedParameterReference", ParameterReferenceManager.EMPTY, null);
         getRootGroup().setParameterContext(parameterContext);
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(new ParameterDescriptor.Builder().name("foo").build(), "bar"));
-        parameters.add(new Parameter(new ParameterDescriptor.Builder().name("sensitive").sensitive(true).build(), "*password*"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("foo", new Parameter(new ParameterDescriptor.Builder().name("foo").build(), "bar"));
+        parameters.put("sensitive", new Parameter(new ParameterDescriptor.Builder().name("sensitive").sensitive(true).build(), "*password*"));
         parameterContext.setParameters(parameters);
 
         verifyText(procNode, "hello", "hello");
@@ -107,9 +106,9 @@ public class ProcessorParameterTokenIT extends FrameworkIntegrationTest {
         final ParameterContext parameterContext = new StandardParameterContext(UUID.randomUUID().toString(), "testEscapedParameterReference", ParameterReferenceManager.EMPTY, null);
         getRootGroup().setParameterContext(parameterContext);
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(new ParameterDescriptor.Builder().name("foo").build(), "bar"));
-        parameters.add(new Parameter(new ParameterDescriptor.Builder().name("sensitive").sensitive(true).build(), "*password*"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("foo", new Parameter(new ParameterDescriptor.Builder().name("foo").build(), "bar"));
+        parameters.put("sensitive", new Parameter(new ParameterDescriptor.Builder().name("sensitive").sensitive(true).build(), "*password*"));
         parameterContext.setParameters(parameters);
 
         verifyCannotSetParameter(procNode, "#{sensitive}", null);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/versioned/ImportFlowIT.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/versioned/ImportFlowIT.java
index 5206315..99f91b8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/versioned/ImportFlowIT.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/integration/versioned/ImportFlowIT.java
@@ -34,6 +34,8 @@ import org.apache.nifi.registry.flow.VersionedControllerService;
 import org.apache.nifi.registry.flow.VersionedFlow;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.flow.VersionedParameter;
+import org.apache.nifi.registry.flow.VersionedParameterContext;
 import org.apache.nifi.registry.flow.VersionedProcessGroup;
 import org.apache.nifi.registry.flow.VersionedProcessor;
 import org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper;
@@ -44,6 +46,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
@@ -70,7 +73,7 @@ public class ImportFlowIT extends FrameworkIntegrationTest {
         processor.setAutoTerminatedRelationships(Collections.singleton(REL_SUCCESS));
         processor.setProperties(Collections.singletonMap(NopServiceReferencingProcessor.SERVICE.getName(), controllerService.getIdentifier()));
 
-        final VersionedFlowSnapshot proposedFlow = createFlowSnapshot(Collections.singletonList(controllerService), Collections.singletonList(processor));
+        final VersionedFlowSnapshot proposedFlow = createFlowSnapshot(Collections.singletonList(controllerService), Collections.singletonList(processor), null);
 
         // Create an Inner Process Group and update it to match the Versioned Flow.
         final ProcessGroup innerGroup = getFlowController().getFlowManager().createProcessGroup("inner-group-id");
@@ -107,7 +110,7 @@ public class ImportFlowIT extends FrameworkIntegrationTest {
     }
 
 
-    private VersionedFlowSnapshot createFlowSnapshot(final List<ControllerServiceNode> controllerServices, final List<ProcessorNode> processors) {
+    private VersionedFlowSnapshot createFlowSnapshot(final List<ControllerServiceNode> controllerServices, final List<ProcessorNode> processors, final Map<String, String> parameters) {
         final VersionedFlowSnapshotMetadata snapshotMetadata = new VersionedFlowSnapshotMetadata();
         snapshotMetadata.setAuthor("unit-test");
         snapshotMetadata.setBucketIdentifier("unit-test-bucket");
@@ -159,6 +162,21 @@ public class ImportFlowIT extends FrameworkIntegrationTest {
         versionedFlowSnapshot.setFlow(flow);
         versionedFlowSnapshot.setFlowContents(flowContents);
 
+        if (parameters != null) {
+            final Set<VersionedParameter> versionedParameters = new HashSet<>();
+            for (final Map.Entry<String, String> entry : parameters.entrySet()) {
+                final VersionedParameter versionedParameter = new VersionedParameter();
+                versionedParameter.setName(entry.getKey());
+                versionedParameter.setValue(entry.getValue());
+                versionedParameters.add(versionedParameter);
+            }
+
+            final VersionedParameterContext versionedParameterContext = new VersionedParameterContext();
+            versionedParameterContext.setName("Unit Test Context");
+            versionedParameterContext.setParameters(versionedParameters);
+            versionedFlowSnapshot.setParameterContexts(Collections.singletonMap("unit-test-context", versionedParameterContext));
+        }
+
         return versionedFlowSnapshot;
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
index 1b211a2..6008e63 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
@@ -27,7 +27,6 @@ import org.mockito.Mockito;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -47,9 +46,9 @@ public class TestStandardParameterContext {
         final ParameterDescriptor xyzDescriptor = new ParameterDescriptor.Builder().name("xyz").build();
         final ParameterDescriptor fooDescriptor = new ParameterDescriptor.Builder().name("foo").description("bar").sensitive(true).build();
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(abcDescriptor, "123"));
-        parameters.add(new Parameter(xyzDescriptor, "242526"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
+        parameters.put("xyz", new Parameter(xyzDescriptor, "242526"));
 
         context.setParameters(parameters);
 
@@ -63,15 +62,15 @@ public class TestStandardParameterContext {
         assertNull(xyzParam.getDescriptor().getDescription());
         assertEquals("242526", xyzParam.getValue());
 
-        final Set<Parameter> secondParameters = new HashSet<>();
-        secondParameters.add(new Parameter(fooDescriptor, "baz"));
+        final Map<String, Parameter> secondParameters = new HashMap<>();
+        secondParameters.put("foo", new Parameter(fooDescriptor, "baz"));
         context.setParameters(secondParameters);
 
         assertTrue(context.getParameter("abc").isPresent());
         assertTrue(context.getParameter("xyz").isPresent());
 
-        secondParameters.add(new Parameter(abcParam.getDescriptor(), null));
-        secondParameters.add(new Parameter(xyzParam.getDescriptor(), null));
+        secondParameters.put("abc", null);
+        secondParameters.put("xyz", null);
 
         context.setParameters(secondParameters);
 
@@ -86,8 +85,8 @@ public class TestStandardParameterContext {
 
         assertEquals(Collections.singletonMap(fooDescriptor, fooParam), context.getParameters());
 
-        final Set<Parameter> thirdParameters = new HashSet<>();
-        thirdParameters.add(new Parameter(fooDescriptor, "other"));
+        final Map<String, Parameter> thirdParameters = new HashMap<>();
+        thirdParameters.put("foo", new Parameter(fooDescriptor, "other"));
         context.setParameters(thirdParameters);
 
         assertEquals("other", context.getParameter("foo").get().getValue());
@@ -100,8 +99,8 @@ public class TestStandardParameterContext {
 
         final ParameterDescriptor abcDescriptor = new ParameterDescriptor.Builder().name("abc").description("abc").build();
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(abcDescriptor, "123"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
 
         context.setParameters(parameters);
 
@@ -112,7 +111,7 @@ public class TestStandardParameterContext {
 
         ParameterDescriptor updatedDescriptor = new ParameterDescriptor.Builder().name("abc").description("Updated").build();
         final Parameter newDescriptionParam = new Parameter(updatedDescriptor, "321");
-        context.setParameters(Collections.singleton(newDescriptionParam));
+        context.setParameters(Collections.singletonMap("abc", newDescriptionParam));
 
         abcParam = context.getParameter("abc").get();
         assertEquals(abcDescriptor, abcParam.getDescriptor());
@@ -121,12 +120,12 @@ public class TestStandardParameterContext {
 
         updatedDescriptor = new ParameterDescriptor.Builder().name("abc").description("Updated Again").build();
         final Parameter paramWithoutValue = new Parameter(updatedDescriptor, null);
-        context.setParameters(Collections.singleton(paramWithoutValue));
+        context.setParameters(Collections.singletonMap("abc", paramWithoutValue));
 
         abcParam = context.getParameter("abc").get();
         assertEquals(abcDescriptor, abcParam.getDescriptor());
         assertEquals("Updated Again", abcParam.getDescriptor().getDescription());
-        assertEquals("321", abcParam.getValue());
+        assertNull(abcParam.getValue());
     }
 
     @Test
@@ -139,17 +138,17 @@ public class TestStandardParameterContext {
         final ParameterDescriptor xyzDescriptor = new ParameterDescriptor.Builder().name("xyz").build();
         final ParameterDescriptor fooDescriptor = new ParameterDescriptor.Builder().name("foo").description("bar").sensitive(true).build();
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(abcDescriptor, "123"));
-        parameters.add(new Parameter(xyzDescriptor, "242526"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
+        parameters.put("xyz", new Parameter(xyzDescriptor, "242526"));
 
         context.setParameters(parameters);
 
         final ParameterDescriptor sensitiveXyzDescriptor = new ParameterDescriptor.Builder().name("xyz").sensitive(true).build();
 
-        final Set<Parameter> updatedParameters = new HashSet<>();
-        updatedParameters.add(new Parameter(fooDescriptor, "baz"));
-        updatedParameters.add(new Parameter(sensitiveXyzDescriptor, "242526"));
+        final Map<String, Parameter> updatedParameters = new HashMap<>();
+        updatedParameters.put("foo", new Parameter(fooDescriptor, "baz"));
+        updatedParameters.put("xyz", new Parameter(sensitiveXyzDescriptor, "242526"));
 
         try {
             context.setParameters(updatedParameters);
@@ -159,7 +158,7 @@ public class TestStandardParameterContext {
 
         final ParameterDescriptor insensitiveAbcDescriptor = new ParameterDescriptor.Builder().name("abc").sensitive(false).build();
         updatedParameters.clear();
-        updatedParameters.add(new Parameter(insensitiveAbcDescriptor, "123"));
+        updatedParameters.put("abc", new Parameter(insensitiveAbcDescriptor, "123"));
 
         try {
             context.setParameters(updatedParameters);
@@ -179,13 +178,13 @@ public class TestStandardParameterContext {
 
         final ParameterDescriptor abcDescriptor = new ParameterDescriptor.Builder().name("abc").sensitive(true).build();
 
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(abcDescriptor, "123"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
 
         context.setParameters(parameters);
 
         parameters.clear();
-        parameters.add(new Parameter(abcDescriptor, "321"));
+        parameters.put("abc", new Parameter(abcDescriptor, "321"));
         context.setParameters(parameters);
 
         assertEquals("321", context.getParameter("abc").get().getValue());
@@ -194,7 +193,7 @@ public class TestStandardParameterContext {
         Mockito.when(procNode.isRunning()).thenReturn(true);
 
         parameters.clear();
-        parameters.add(new Parameter(abcDescriptor, "123"));
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
 
         try {
             context.setParameters(parameters);
@@ -202,10 +201,10 @@ public class TestStandardParameterContext {
         } catch (final IllegalStateException expected) {
         }
 
-        context.setParameters(Collections.emptySet());
+        context.setParameters(Collections.emptyMap());
 
         parameters.clear();
-        parameters.add(new Parameter(abcDescriptor, null));
+        parameters.put("abc", new Parameter(abcDescriptor, null));
         try {
             context.setParameters(parameters);
             Assert.fail("Was able to remove parameter while referencing processor was running");
@@ -224,15 +223,15 @@ public class TestStandardParameterContext {
         Mockito.when(serviceNode.getState()).thenReturn(ControllerServiceState.ENABLED);
 
         final ParameterDescriptor abcDescriptor = new ParameterDescriptor.Builder().name("abc").sensitive(true).build();
-        final Set<Parameter> parameters = new HashSet<>();
-        parameters.add(new Parameter(abcDescriptor, "123"));
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameters.put("abc", new Parameter(abcDescriptor, "123"));
 
         context.setParameters(parameters);
 
         referenceManager.addControllerServiceReference("abc", serviceNode);
 
         parameters.clear();
-        parameters.add(new Parameter(abcDescriptor, "321"));
+        parameters.put("abc", new Parameter(abcDescriptor, "321"));
 
         for (final ControllerServiceState state : EnumSet.of(ControllerServiceState.ENABLED, ControllerServiceState.ENABLING, ControllerServiceState.DISABLING)) {
             Mockito.when(serviceNode.getState()).thenReturn(state);
@@ -249,7 +248,7 @@ public class TestStandardParameterContext {
         parameters.clear();
         context.setParameters(parameters);
 
-        parameters.add(new Parameter(abcDescriptor, null));
+        parameters.put("abc", new Parameter(abcDescriptor, null));
         try {
             context.setParameters(parameters);
             Assert.fail("Was able to remove parameter being referenced by Controller Service that is DISABLING");
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
index 2516b42..809eaf3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/int-tests/clustered-nifi.properties
@@ -218,7 +218,7 @@ nifi.cluster.flow.election.max.candidates=
 
 # cluster load balancing properties #
 nifi.cluster.load.balance.host=
-nifi.cluster.load.balance.port=6342
+nifi.cluster.load.balance.port=0
 nifi.cluster.load.balance.connections.per.node=4
 nifi.cluster.load.balance.max.thread.count=8
 nifi.cluster.load.balance.comms.timeout=30 sec
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index 34670e0..7594fa8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -1046,10 +1046,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         final Set<ProcessGroup> boundProcessGroups = parameterContext.getParameterReferenceManager().getProcessGroupsBound(parameterContext);
 
         final ParameterContext updatedParameterContext = new StandardParameterContext(parameterContext.getIdentifier(), parameterContext.getName(), ParameterReferenceManager.EMPTY, null);
-        final Set<Parameter> parameters = parameterContextDto.getParameters().stream()
+        final Map<String, Parameter> parameters = new HashMap<>();
+        parameterContextDto.getParameters().stream()
             .map(ParameterEntity::getParameter)
             .map(this::createParameter)
-            .collect(Collectors.toSet());
+            .forEach(param -> parameters.put(param.getDescriptor().getName(), param));
         updatedParameterContext.setParameters(parameters);
 
         final List<ComponentValidationResultEntity> validationResults = new ArrayList<>();
@@ -1089,6 +1090,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     }
 
     private Parameter createParameter(final ParameterDTO dto) {
+        if (dto.getDescription() == null && dto.getSensitive() == null && dto.getValue() == null) {
+            return null; // null description, sensitivity flag, and value indicates a deletion, which we want to represent as a null Parameter.
+        }
+
         final ParameterDescriptor descriptor = new ParameterDescriptor.Builder()
             .name(dto.getName())
             .description(dto.getDescription())
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
index aaab830..4b02b9d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
@@ -37,10 +37,10 @@ import org.apache.nifi.web.api.entity.ParameterEntity;
 import org.apache.nifi.web.dao.ParameterContextDAO;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 public class StandardParameterContextDAO implements ParameterContextDAO {
     private FlowManager flowManager;
@@ -57,7 +57,7 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
 
     @Override
     public ParameterContext createParameterContext(final ParameterContextDTO parameterContextDto) {
-        final Set<Parameter> parameters = getParameters(parameterContextDto);
+        final Map<String, Parameter> parameters = getParameters(parameterContextDto);
         final ParameterContext parameterContext = flowManager.createParameterContext(parameterContextDto.getId(), parameterContextDto.getName(), parameters);
         if (parameterContextDto.getDescription() != null) {
             parameterContext.setDescription(parameterContextDto.getDescription());
@@ -65,16 +65,30 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
         return parameterContext;
     }
 
-    private Set<Parameter> getParameters(final ParameterContextDTO parameterContextDto) {
-        final Set<ParameterEntity> parameterDtos = parameterContextDto.getParameters();
-        if (parameterDtos == null) {
-            return Collections.emptySet();
+    private Map<String, Parameter> getParameters(final ParameterContextDTO parameterContextDto) {
+        final Set<ParameterEntity> parameterEntities = parameterContextDto.getParameters();
+        if (parameterEntities == null) {
+            return Collections.emptyMap();
         }
 
-        return parameterContextDto.getParameters().stream()
-            .map(ParameterEntity::getParameter)
-            .map(this::createParameter)
-            .collect(Collectors.toSet());
+        final Map<String, Parameter> parameterMap = new HashMap<>();
+        for (final ParameterEntity parameterEntity : parameterEntities) {
+            final ParameterDTO parameterDto = parameterEntity.getParameter();
+
+            if (parameterDto.getName() == null) {
+                throw new IllegalArgumentException("Cannot specify a Parameter without a name");
+            }
+
+            final boolean deletion = parameterDto.getDescription() == null && parameterDto.getSensitive() == null && parameterDto.getValue() == null;
+            if (deletion) {
+                parameterMap.put(parameterDto.getName(), null);
+            } else {
+                final Parameter parameter = createParameter(parameterDto);
+                parameterMap.put(parameterDto.getName(), parameter);
+            }
+        }
+
+        return parameterMap;
     }
 
     private Parameter createParameter(final ParameterDTO dto) {
@@ -118,7 +132,7 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
         }
 
         if (parameterContextDto.getParameters() != null) {
-            final Set<Parameter> parameters = getParameters(parameterContextDto);
+            final Map<String, Parameter> parameters = getParameters(parameterContextDto);
             context.setParameters(parameters);
         }
 
diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/impl/ValidationContextAdapter.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/impl/ValidationContextAdapter.java
index b983c16..eee7e37 100644
--- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/impl/ValidationContextAdapter.java
+++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/impl/ValidationContextAdapter.java
@@ -101,6 +101,11 @@ public abstract class ValidationContextAdapter implements ValidationContext {
     }
 
     @Override
+    public boolean isParameterSet(final String parameterName) {
+        return innerValidationContext.isParameterSet(parameterName);
+    }
+
+    @Override
     public Collection<String> getReferencedParameters(final String propertyName) {
         return innerValidationContext.getReferencedParameters(propertyName);
     }
diff --git a/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessParameterContext.java b/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessParameterContext.java
index e48a965..2a9ad4f 100644
--- a/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessParameterContext.java
+++ b/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessParameterContext.java
@@ -61,13 +61,13 @@ public class StatelessParameterContext implements ParameterContext {
     }
 
     @Override
-    public void setParameters(final Set<Parameter> updatedParameters) {
-        throw new UnsupportedOperationException();
+    public void setParameters(final Map<String, Parameter> updatedParameters) {
+        throw new UnsupportedOperationException(); // This parameter context does not support updating - all parameters are provided in the constructor.
     }
 
     @Override
-    public void verifyCanSetParameters(final Set<Parameter> parameters) {
-        throw new UnsupportedOperationException();
+    public void verifyCanSetParameters(final Map<String, Parameter> parameters) {
+        throw new UnsupportedOperationException(); // This parameter context does not support updating - all parameters are provided in the constructor.
     }
 
     @Override
diff --git a/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessValidationContext.java b/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessValidationContext.java
index 81f1eb0..9ed8434 100644
--- a/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessValidationContext.java
+++ b/nifi-stateless/nifi-stateless-core/src/main/java/org/apache/nifi/stateless/core/StatelessValidationContext.java
@@ -26,6 +26,7 @@ import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.ControllerServiceLookup;
 import org.apache.nifi.controller.PropertyConfiguration;
 import org.apache.nifi.expression.ExpressionLanguageCompiler;
+import org.apache.nifi.parameter.Parameter;
 import org.apache.nifi.parameter.ParameterContext;
 import org.apache.nifi.parameter.ParameterReference;
 import org.apache.nifi.registry.VariableRegistry;
@@ -36,6 +37,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public class StatelessValidationContext implements ValidationContext {
@@ -147,6 +149,22 @@ public class StatelessValidationContext implements ValidationContext {
     }
 
     @Override
+    public boolean isParameterSet(final String parameterName) {
+        if (parameterContext == null) {
+            return false;
+        }
+
+        final Optional<Parameter> parameterOption = parameterContext.getParameter(parameterName);
+        if (!parameterOption.isPresent()) {
+            return false;
+        }
+
+        final String value = parameterOption.get().getValue();
+        return value != null;
+
+    }
+
+    @Override
     public ControllerServiceLookup getControllerServiceLookup() {
         return this.lookup;
     }