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

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


The following commit(s) were added to refs/heads/main by this push:
     new 4ff9cdd  NIFI-8173, NIFI-8174: This closes #4784. Updated Parameter 
Contexts to allow for unsetting a parameter value / setting it to null. Allow 
Parameters to make use of Expression Language. Updated docs to illustrated how 
EL is evaluated
4ff9cdd is described below

commit 4ff9cddf159e974c7a5a5c9aa9e238c520d37dc3
Author: Mark Payne <[email protected]>
AuthorDate: Tue Jan 26 14:54:17 2021 -0500

    NIFI-8173, NIFI-8174: This closes #4784. Updated Parameter Contexts to 
allow for unsetting a parameter value / setting it to null. Allow Parameters to 
make use of Expression Language. Updated docs to illustrated how EL is evaluated
    
    Signed-off-by: Joe Witt <[email protected]>
---
 .../nifi/parameter/StandardParameterReference.java |   2 +-
 .../nifi/parameter/StandardParameterTokenList.java |  15 +-
 .../parameter/TestStandardParameterTokenList.java  |   3 +-
 nifi-docs/src/main/asciidoc/user-guide.adoc        |  25 ++-
 .../org/apache/nifi/web/api/dto/ParameterDTO.java  |  11 ++
 .../nifi/parameter/StandardParameterContext.java   |   7 +-
 .../nifi/processor/StandardProcessContext.java     |   2 +-
 .../parameter/TestStandardParameterContext.java    |   7 +-
 .../nifi/controller/AbstractComponentNode.java     |  10 --
 .../serialization/StandardFlowSerializer.java      |  45 ++---
 .../src/main/resources/FlowConfiguration.xsd       |   2 +-
 .../web/dao/impl/StandardParameterContextDAO.java  |  25 ++-
 .../webapp/js/nf/canvas/nf-parameter-contexts.js   |  16 +-
 .../EvaluatePropertiesWithDifferentELScopes.java   |  97 ++++++++++
 .../nifi/processors/tests/system/WriteToFile.java  | 103 +++++++++++
 .../services/org.apache.nifi.processor.Processor   |   2 +
 .../system/parameters/ParameterContextIT.java      | 198 ++++++++++++++++++++-
 17 files changed, 510 insertions(+), 60 deletions(-)

diff --git 
a/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterReference.java
 
b/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterReference.java
index 4f270f1..ec596e6 100644
--- 
a/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterReference.java
+++ 
b/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterReference.java
@@ -68,6 +68,6 @@ public class StandardParameterReference implements 
ParameterReference {
         }
 
         final Optional<Parameter> parameter = 
parameterLookup.getParameter(parameterName);
-        return parameter.map(Parameter::getValue).orElse(referenceText);
+        return parameter.map(Parameter::getValue).orElse(null);
     }
 }
diff --git 
a/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterTokenList.java
 
b/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterTokenList.java
index f9c0a0f..3959895 100644
--- 
a/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterTokenList.java
+++ 
b/nifi-commons/nifi-parameter/src/main/java/org/apache/nifi/parameter/StandardParameterTokenList.java
@@ -76,12 +76,18 @@ public class StandardParameterTokenList implements 
ParameterTokenList {
     private String substitute(final Function<ParameterToken, String> 
transform) {
         final StringBuilder sb = new StringBuilder();
 
+        int nullCount = 0;
         int lastEndOffset = -1;
         for (final ParameterToken token : tokens) {
             final int startOffset = token.getStartOffset();
 
             sb.append(input, lastEndOffset + 1, startOffset);
-            sb.append(transform.apply(token));
+            final String transformed = transform.apply(token);
+            if (transformed == null) {
+                nullCount++;
+            } else {
+                sb.append(transformed);
+            }
 
             lastEndOffset = token.getEndOffset();
         }
@@ -90,7 +96,12 @@ public class StandardParameterTokenList implements 
ParameterTokenList {
             sb.append(input, lastEndOffset + 1, input.length());
         }
 
-        return sb.toString();
+        final String substituted = sb.toString();
+        if (nullCount == tokens.size() && !tokens.isEmpty() && 
substituted.isEmpty()) {
+            return null;
+        }
+
+        return substituted;
     }
 
     @Override
diff --git 
a/nifi-commons/nifi-parameter/src/test/java/org/apache/nifi/parameter/TestStandardParameterTokenList.java
 
b/nifi-commons/nifi-parameter/src/test/java/org/apache/nifi/parameter/TestStandardParameterTokenList.java
index bfac611..7f5afe7 100644
--- 
a/nifi-commons/nifi-parameter/src/test/java/org/apache/nifi/parameter/TestStandardParameterTokenList.java
+++ 
b/nifi-commons/nifi-parameter/src/test/java/org/apache/nifi/parameter/TestStandardParameterTokenList.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Optional;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 public class TestStandardParameterTokenList {
 
@@ -60,7 +61,7 @@ public class TestStandardParameterTokenList {
         
Mockito.when(paramContext.getParameter(Mockito.anyString())).thenReturn(Optional.empty());
         final StandardParameterTokenList references = new 
StandardParameterTokenList("#{foo}", referenceList);
 
-        assertEquals("#{foo}", references.substitute(paramContext));
+        assertNull(references.substitute(paramContext));
     }
 
     @Test
diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc 
b/nifi-docs/src/main/asciidoc/user-guide.adoc
index f5647d4..88d9d7f 100644
--- a/nifi-docs/src/main/asciidoc/user-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/user-guide.adoc
@@ -881,7 +881,8 @@ The Add Parameter window has the following settings:
 
 - *Name* - A name that is used to denote the Parameter. Only alpha-numeric 
characters (a-z, A-Z, 0-9), hyphens ( - ), underscores ( _ ), periods ( . ), 
and spaces are allowed.
 
-- *Value* - The value that will be used when the Parameter is referenced. 
Parameter values do not support Expression Language or embedded parameter 
references.
+- *Value* - The value that will be used when the Parameter is referenced. If a 
Parameter makes use of the Expression Language, it is important to note that 
the Expression Language will be evaluated
+in the context of the component that references the Parameter. Please see the 
<<parameters-and-el>> section below for more information.
 
 - *Set empty string* - Check to explicitly set the value of the Parameter to 
an empty string. Unchecked by default. (Note: If checked but a value is set, 
the checkbox is ignored.)
 
@@ -900,6 +901,28 @@ image:parameters-validate-affected-components.png[Validate 
Affected Components]
 The Referencing Components section now lists an aggregation of all the 
components referenced by the set of parameters added/edited/deleted, organized 
by process group.
 
 
+[[parameters-and-el]]
+==== Parameters and Expression Language
+
+When adding a Parameter that makes use of the Expression Language, it is 
important to understand the context in which the Expression Language will be 
evaluated. The expression is always evaluated
+in the context of the Process or Controller Service that references the 
Parameter. Take, for example, a scenario where Parameter with the name `Time` 
is added with a value of `${now()}`. The
+Expression Language results in a call to determine the system time when it is 
evaluated. When added as a Parameter, the system time is not evaluated when the 
Parameter is added, but rather when a
+Processor or Controller Service evaluates the Expression. That is, if a 
Processor has a Property whose value is set to `#{Time}` it will function in 
exactly the same manner as if the Property's
+value were set to `${now()}`. Each time that the property is referenced, it 
will produce a different timestamp.
+
+Furthermore, some Properties do not allow for Expression Language, while 
others allow for Expression Language but do not evaluate expressions against 
FlowFile attributes. To help understand how
+this works, consider a Parameter named `File` whose value is `${filename}`. 
Then consider three different properties, each with a different Expression 
Language Scope and a FlowFile whose filename
+is `test.txt`. If each of those Properties is set to `#{File}`, then the 
follow table illustrates the resultant value.
+
+|===
+| Configured Property Value | Expression Language Scope | Effective Property 
Value | Notes
+
+| #{File} | FlowFile Attributes | test.txt | The filename is resolved by 
looking at the `filename` attribute.
+| #{File} | Variable Registry Only | _Empty String_ | FlowFile attributes are 
not in scope, and we assume there is no Variable in the Variable Registry named 
"filename"
+| #{File} | None | ${filename} | The literal text "${filename}" will be 
unevaluated.
+|===
+
+
 [[assigning_parameter_context_to_PG]]
 ==== Assigning a Parameter Context to a Process Group
 For a component to reference a Parameter, its Process Group must first be 
assigned a Parameter Context. Once assigned, processors and controller services 
within that Process Group may only reference Parameters within that Parameter 
Context.
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java
index 4cbeb6f..7d48bec 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterDTO.java
@@ -28,6 +28,7 @@ public class ParameterDTO {
     private String description;
     private Boolean sensitive;
     private String value;
+    private Boolean valueRemoved;
     private Set<AffectedComponentEntity> referencingComponents;
 
     @ApiModelProperty("The name of the Parameter")
@@ -66,6 +67,16 @@ public class ParameterDTO {
         this.value = value;
     }
 
+    @ApiModelProperty("Whether or not the value of the Parameter was removed. 
When a request is made to change a parameter, the value may be null. The 
absence of the value may be used either to " +
+        "indicate that the value is not to be changed, or that the value is to 
be set to null (i.e., removed). This denotes which of the two scenarios is 
being encountered.")
+    public Boolean getValueRemoved() {
+        return valueRemoved;
+    }
+
+    public void setValueRemoved(final Boolean valueRemoved) {
+        this.valueRemoved = valueRemoved;
+    }
+
     @ApiModelProperty("The set of all components in the flow that are 
referencing this Parameter")
     public Set<AffectedComponentEntity> getReferencingComponents() {
         return referencingComponents;
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
index 6485827..763e0fd 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java
@@ -172,12 +172,7 @@ public class StandardParameterContext implements 
ParameterContext {
     }
 
     private String getFullyPopulatedValue(final Parameter proposedParameter) {
-        if (proposedParameter.getValue() != null) {
-            return proposedParameter.getValue();
-        }
-
-        final Parameter oldParameter = 
parameters.get(proposedParameter.getDescriptor());
-        return oldParameter == null ? null : oldParameter.getValue();
+        return proposedParameter.getValue();
     }
 
     private ParameterDescriptor getFullyPopulatedDescriptor(final Parameter 
proposedParameter) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
index d7030ef..ca66598 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
@@ -67,7 +67,7 @@ public class StandardProcessContext implements 
ProcessContext, ControllerService
         properties = 
Collections.unmodifiableMap(processorNode.getEffectivePropertyValues());
 
         preparedQueries = new HashMap<>();
-        for (final Map.Entry<PropertyDescriptor, String> entry : 
processorNode.getRawPropertyValues().entrySet()) {
+        for (final Map.Entry<PropertyDescriptor, String> entry : 
properties.entrySet()) {
             final PropertyDescriptor desc = entry.getKey();
             String value = entry.getValue();
             if (value == null) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
index 2be492e..15b268f 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/parameter/TestStandardParameterContext.java
@@ -33,6 +33,7 @@ import java.util.Set;
 import static junit.framework.TestCase.assertTrue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 public class TestStandardParameterContext {
 
@@ -53,12 +54,12 @@ public class TestStandardParameterContext {
 
         final Parameter abcParam = context.getParameter("abc").get();
         assertEquals(abcDescriptor, abcParam.getDescriptor());
-        Assert.assertNull(abcParam.getDescriptor().getDescription());
+        assertNull(abcParam.getDescriptor().getDescription());
         assertEquals("123", abcParam.getValue());
 
         final Parameter xyzParam = context.getParameter("xyz").get();
         assertEquals(xyzDescriptor, xyzParam.getDescriptor());
-        Assert.assertNull(xyzParam.getDescriptor().getDescription());
+        assertNull(xyzParam.getDescriptor().getDescription());
         assertEquals("242526", xyzParam.getValue());
 
         final Map<String, Parameter> secondParameters = new HashMap<>();
@@ -124,7 +125,7 @@ public class TestStandardParameterContext {
         abcParam = context.getParameter("abc").get();
         assertEquals(abcDescriptor, abcParam.getDescriptor());
         assertEquals("Updated Again", 
abcParam.getDescriptor().getDescription());
-        assertEquals("321", abcParam.getValue());
+        assertNull(abcParam.getValue());
     }
 
     @Test
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 9c44f7a..a40c729 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
@@ -673,16 +673,6 @@ public abstract class AbstractComponentNode implements 
ComponentNode {
                         .valid(false)
                         .explanation("Property references Parameter '" + 
paramName + "' but the currently selected Parameter Context does not have a 
Parameter with that name")
                         .build());
-
-                    continue;
-                }
-
-                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/src/main/java/org/apache/nifi/controller/serialization/StandardFlowSerializer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardFlowSerializer.java
index 950da5c..b382930 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardFlowSerializer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/StandardFlowSerializer.java
@@ -16,22 +16,6 @@
  */
 package org.apache.nifi.controller.serialization;
 
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.connectable.ConnectableType;
@@ -72,6 +56,23 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Serializes a Flow Controller as XML to an output stream.
  *
@@ -175,11 +176,13 @@ public class StandardFlowSerializer implements 
FlowSerializer<Document> {
         addStringElement(parameterElement, "description", 
descriptor.getDescription());
         addStringElement(parameterElement, "sensitive", 
String.valueOf(descriptor.isSensitive()));
 
-        if (descriptor.isSensitive()) {
-            final String parameterValue = parameter.getValue();
-            addStringElement(parameterElement, "value", parameterValue == null 
? null : ENC_PREFIX + encryptor.encrypt(parameterValue) + ENC_SUFFIX);
-        } else {
-            addStringElement(parameterElement, "value", parameter.getValue());
+        if (parameter.getValue() != null) {
+            if (descriptor.isSensitive()) {
+                final String parameterValue = parameter.getValue();
+                addStringElement(parameterElement, "value", parameterValue == 
null ? null : ENC_PREFIX + encryptor.encrypt(parameterValue) + ENC_SUFFIX);
+            } else {
+                addStringElement(parameterElement, "value", 
parameter.getValue());
+            }
         }
     }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/resources/FlowConfiguration.xsd
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/resources/FlowConfiguration.xsd
index 36d7d92..fd3577b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/resources/FlowConfiguration.xsd
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/resources/FlowConfiguration.xsd
@@ -77,7 +77,7 @@
             <xs:element name="name" type="NonEmptyStringType" />
             <xs:element name="description" type="xs:string" />
             <xs:element name="sensitive" type="xs:boolean" />
-            <xs:element name="value" type="xs:string" />
+            <xs:element name="value" type="xs:string" minOccurs="0" 
maxOccurs="1" />
         </xs:sequence>
     </xs:complexType>
 
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 bb5ec9a..583806e 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
@@ -40,6 +40,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 public class StandardParameterContextDAO implements ParameterContextDAO {
@@ -57,7 +58,7 @@ public class StandardParameterContextDAO implements 
ParameterContextDAO {
 
     @Override
     public ParameterContext createParameterContext(final ParameterContextDTO 
parameterContextDto) {
-        final Map<String, Parameter> parameters = 
getParameters(parameterContextDto);
+        final Map<String, Parameter> parameters = 
getParameters(parameterContextDto, null);
         final ParameterContext parameterContext = 
flowManager.createParameterContext(parameterContextDto.getId(), 
parameterContextDto.getName(), parameters);
         if (parameterContextDto.getDescription() != null) {
             
parameterContext.setDescription(parameterContextDto.getDescription());
@@ -65,7 +66,7 @@ public class StandardParameterContextDAO implements 
ParameterContextDAO {
         return parameterContext;
     }
 
-    private Map<String, Parameter> getParameters(final ParameterContextDTO 
parameterContextDto) {
+    private Map<String, Parameter> getParameters(final ParameterContextDTO 
parameterContextDto, final ParameterContext context) {
         final Set<ParameterEntity> parameterEntities = 
parameterContextDto.getParameters();
         if (parameterEntities == null) {
             return Collections.emptyMap();
@@ -83,7 +84,7 @@ public class StandardParameterContextDAO implements 
ParameterContextDAO {
             if (deletion) {
                 parameterMap.put(parameterDto.getName().trim(), null);
             } else {
-                final Parameter parameter = createParameter(parameterDto);
+                final Parameter parameter = createParameter(parameterDto, 
context);
                 parameterMap.put(parameterDto.getName().trim(), parameter);
             }
         }
@@ -91,14 +92,26 @@ public class StandardParameterContextDAO implements 
ParameterContextDAO {
         return parameterMap;
     }
 
-    private Parameter createParameter(final ParameterDTO dto) {
+    private Parameter createParameter(final ParameterDTO dto, final 
ParameterContext context) {
         final ParameterDescriptor descriptor = new 
ParameterDescriptor.Builder()
             .name(dto.getName())
             .description(dto.getDescription())
             .sensitive(Boolean.TRUE.equals(dto.getSensitive()))
             .build();
 
-        return new Parameter(descriptor, dto.getValue());
+        final String value;
+        if (dto.getValue() == null && 
Boolean.TRUE.equals(dto.getValueRemoved())) {
+            // Value is being explicitly set to null
+            value = null;
+        } else if (dto.getValue() == null && context != null) {
+            // Value was just never supplied. Use the value from the Parameter 
Context, if there is one.
+            final Optional<Parameter> optionalParameter = 
context.getParameter(dto.getName());
+            value = 
optionalParameter.map(Parameter::getValue).orElse(dto.getValue());
+        } else {
+            value = dto.getValue();
+        }
+
+        return new Parameter(descriptor, value);
     }
 
     @Override
@@ -132,7 +145,7 @@ public class StandardParameterContextDAO implements 
ParameterContextDAO {
         }
 
         if (parameterContextDto.getParameters() != null) {
-            final Map<String, Parameter> parameters = 
getParameters(parameterContextDto);
+            final Map<String, Parameter> parameters = 
getParameters(parameterContextDto, context);
             context.setParameters(parameters);
         }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
index c86df4d..7bbe867 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
@@ -270,6 +270,13 @@
                 // if modified grab what's changed
                 if (this.hasValueChanged) {
                     parameter['value'] = this.value;
+
+                    if (_.isEmpty(this.value) && !this.isEmptyStringSet) {
+                        // No value is set, and the 'Set empty string' 
checkbox was not set. The value is expected to be null.
+                        // Because sensitive parameter values are not known, 
when a sensitive parameter is updated, its value is send back as null.
+                        // To disambiguate between a value explicitly being 
null, an empty string, and a value not be provided, we need to set the 
'valueRemoved' flag.
+                        parameter['valueRemoved'] = true;
+                    }
                 }
 
                 parameter['description'] = this.description;
@@ -854,12 +861,9 @@
         var isSensitive = 
$('#parameter-dialog').find('input[name="sensitive"]:checked').val() === 
'sensitive' ? true : false;
 
         var validateValue = function () {
-            // updates to a parameter cannot have a null value
-            if (!this.isNew) {
-                if (_.isEmpty(this.value) && !this.isEmptyStringSet) {
-                    return false;
-                }
-            }
+            // in previous versions, updates to a parameter were not allowed 
to have a null value.
+            // This has changed, but the validation function is left intact 
for now because it is likely that
+            // we may introduce additional validation in the future.
             return true;
         };
 
diff --git 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EvaluatePropertiesWithDifferentELScopes.java
 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EvaluatePropertiesWithDifferentELScopes.java
new file mode 100644
index 0000000..dfc071b
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EvaluatePropertiesWithDifferentELScopes.java
@@ -0,0 +1,97 @@
+/*
+ * 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.processors.tests.system;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class EvaluatePropertiesWithDifferentELScopes extends AbstractProcessor 
{
+    static final PropertyDescriptor EVALUATE_FLOWFILE_CONTEXT = new 
PropertyDescriptor.Builder()
+        .name("FlowFile Context")
+        .description("The value of the property will be evaluated with 
FlowFile attributes")
+        .required(false)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+        .build();
+    static final PropertyDescriptor EVALUATE_VARIABLE_REGISTRY_CONTEXT = new 
PropertyDescriptor.Builder()
+        .name("Variable Registry Context")
+        .description("The value of the property will be evaluated with only 
with the Variable Registry")
+        .required(false)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
+        .build();
+    static final PropertyDescriptor EVALUATE_NO_EL_CONTEXT = new 
PropertyDescriptor.Builder()
+        .name("Expression Language Not Evaluated")
+        .description("The value of the property will be evaluated without 
evaluating Expression Language")
+        .required(false)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+        .build();
+
+    public static final Relationship REL_SUCCESS = new Relationship.Builder()
+        .name("success")
+        .build();
+
+    @Override
+    public Set<Relationship> getRelationships() {
+        return Collections.singleton(REL_SUCCESS);
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return Arrays.asList(EVALUATE_FLOWFILE_CONTEXT, 
EVALUATE_VARIABLE_REGISTRY_CONTEXT, EVALUATE_NO_EL_CONTEXT);
+    }
+
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession 
session) throws ProcessException {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final Integer flowFileContext = 
context.getProperty(EVALUATE_FLOWFILE_CONTEXT).evaluateAttributeExpressions(flowFile).asInteger();
+        final Integer variableRegistryContext = 
context.getProperty(EVALUATE_VARIABLE_REGISTRY_CONTEXT).evaluateAttributeExpressions().asInteger();
+        final String noElContext = 
context.getProperty(EVALUATE_NO_EL_CONTEXT).getValue();
+
+        if (flowFileContext != null) {
+            session.adjustCounter("flowfile", flowFileContext, false);
+        }
+
+        if (variableRegistryContext != null) {
+            session.adjustCounter("variable.registry", 
variableRegistryContext, false);
+        }
+
+        if (noElContext != null && noElContext.matches("[0-9]+")) {
+            session.adjustCounter("no.el.evaluation", 
Integer.parseInt(noElContext), false);
+        }
+
+        session.transfer(flowFile, REL_SUCCESS);
+    }
+}
diff --git 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/WriteToFile.java
 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/WriteToFile.java
new file mode 100644
index 0000000..e5086cc
--- /dev/null
+++ 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/WriteToFile.java
@@ -0,0 +1,103 @@
+/*
+ * 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.processors.tests.system;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyDescriptor.Builder;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.stream.io.StreamUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static 
org.apache.nifi.expression.ExpressionLanguageScope.FLOWFILE_ATTRIBUTES;
+import static 
org.apache.nifi.processor.util.StandardValidators.NON_EMPTY_VALIDATOR;
+
+public class WriteToFile extends AbstractProcessor {
+
+    static final PropertyDescriptor FILENAME = new Builder()
+        .name("Filename")
+        .displayName("Filename")
+        .description("The file to write the FlowFile contents to")
+        .required(true)
+        .addValidator(NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(FLOWFILE_ATTRIBUTES)
+        .defaultValue("target/WriteToFile.out")
+        .build();
+
+    static final Relationship REL_SUCCESS = new Relationship.Builder()
+        .name("success")
+        .build();
+
+    static final Relationship REL_FAILURE = new Relationship.Builder()
+        .name("failure")
+        .build();
+
+    @Override
+    public Set<Relationship> getRelationships() {
+        return new HashSet<>(Arrays.asList(REL_SUCCESS, REL_FAILURE));
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return Collections.singletonList(FILENAME);
+    }
+
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession 
session) throws ProcessException {
+        FlowFile flowFile = session.get();
+        if (flowFile == null) {
+            return;
+        }
+
+        final String filename = 
context.getProperty(FILENAME).evaluateAttributeExpressions(flowFile).getValue();
+        final File file = new File(filename);
+        final File dir = file.getParentFile();
+        if (!dir.exists() && !dir.mkdirs()) {
+            getLogger().error("Could not write FlowFile to {} because the 
directory does not exist and could not be created", file.getAbsolutePath());
+            session.transfer(flowFile, REL_FAILURE);
+            return;
+        }
+
+        try (final OutputStream out = new FileOutputStream(file);
+             final InputStream in = session.read(flowFile)) {
+            StreamUtils.copy(in, out);
+        } catch (final Exception e) {
+            getLogger().error("Could not write FlowFile to {}", 
file.getAbsolutePath(), e);
+            session.transfer(flowFile, REL_FAILURE);
+            return;
+        }
+
+        session.transfer(flowFile, REL_SUCCESS);
+
+        getLogger().info("Wrote one FlowFile of size {} to {}", 
flowFile.getSize(), file.getAbsolutePath());
+        session.getProvenanceReporter().send(flowFile, 
file.toURI().toString());
+    }
+}
diff --git 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index ce4347a..169a9c6 100644
--- 
a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ 
b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -17,6 +17,7 @@ org.apache.nifi.processors.tests.system.CountEvents
 org.apache.nifi.processors.tests.system.CountFlowFiles
 org.apache.nifi.processors.tests.system.DependOnProperties
 org.apache.nifi.processors.tests.system.Duplicate
+org.apache.nifi.processors.tests.system.EvaluatePropertiesWithDifferentELScopes
 org.apache.nifi.processors.tests.system.FakeProcessor
 org.apache.nifi.processors.tests.system.FakeDynamicPropertiesProcessor
 org.apache.nifi.processors.tests.system.GenerateFlowFile
@@ -26,3 +27,4 @@ org.apache.nifi.processors.tests.system.Sleep
 org.apache.nifi.processors.tests.system.TerminateFlowFile
 org.apache.nifi.processors.tests.system.ThrowProcessException
 org.apache.nifi.processors.tests.system.ValidateFileExists
+org.apache.nifi.processors.tests.system.WriteToFile
\ No newline at end of file
diff --git 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
index 2221789..17f07e1 100644
--- 
a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
+++ 
b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ParameterContextIT.java
@@ -31,9 +31,14 @@ import org.apache.nifi.web.api.entity.ProcessGroupEntity;
 import org.apache.nifi.web.api.entity.ProcessorEntity;
 import org.junit.Test;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+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;
 import java.util.stream.Collectors;
@@ -104,7 +109,7 @@ public class ParameterContextIT extends NiFiSystemIT {
     }
 
 
-    @Test(timeout = 30000)
+    @Test
     public void testAddingMissingParameterMakesProcessorValid() throws 
NiFiClientException, IOException, InterruptedException {
         final ProcessorEntity createdProcessorEntity = 
createProcessor(TEST_PROCESSORS_PACKAGE + ".CountEvents", NIFI_GROUP_ID, 
TEST_EXTENSIONS_ARTIFACT_ID, getNiFiVersion());
         final String processorId = createdProcessorEntity.getId();
@@ -125,6 +130,197 @@ public class ParameterContextIT extends NiFiSystemIT {
     }
 
     @Test
+    public void testValidationWithRequiredPropertiesAndDefault() throws 
NiFiClientException, IOException, InterruptedException {
+        final ProcessorEntity generate = 
getClientUtil().createProcessor("GenerateFlowFile");
+        getClientUtil().updateProcessorProperties(generate, 
Collections.singletonMap("File Size", "#{foo}"));
+        getClientUtil().setAutoTerminatedRelationships(generate, "success");
+
+        final String processorId = generate.getId();
+
+        waitForInvalidProcessor(processorId);
+
+        final ParameterEntity fooKB = createParameterEntity("foo", null, 
false, "1 KB");
+        final Set<ParameterEntity> parameters = new HashSet<>();
+        parameters.add(fooKB);
+        final ParameterContextEntity contextEntity = 
createParameterContextEntity(getTestName(), null, parameters);
+        final ParameterContextEntity createdContextEntity = 
getNifiClient().getParamContextClient().createParamContext(contextEntity);
+
+        setParameterContext("root", createdContextEntity);
+        waitForValidProcessor(processorId);
+
+        final ParameterEntity fooNull = createParameterEntity("foo", null, 
false, null);
+        
createdContextEntity.getComponent().setParameters(Collections.singleton(fooNull));
+        
getNifiClient().getParamContextClient().updateParamContext(createdContextEntity);
+        // Should remain valid because property has a default.
+        waitForValidProcessor(processorId);
+    }
+
+    @Test(timeout=30000)
+    public void testValidationWithRequiredPropertiesAndNoDefault() throws 
NiFiClientException, IOException, InterruptedException {
+        final ProcessorEntity generate = 
getClientUtil().createProcessor("DependOnProperties");
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("Always Required", "#{foo}");
+        properties.put("Required If Always Required Is Bar Or Baz", "15");
+        getClientUtil().updateProcessorProperties(generate, properties);
+        final String processorId = generate.getId();
+
+        waitForInvalidProcessor(processorId);
+
+        final ParameterEntity fooBar = createParameterEntity("foo", null, 
false, "bar");
+        final Set<ParameterEntity> parameters = new HashSet<>();
+        parameters.add(fooBar);
+        final ParameterContextEntity contextEntity = 
createParameterContextEntity(getTestName(), null, parameters);
+        final ParameterContextEntity createdContextEntity = 
getNifiClient().getParamContextClient().createParamContext(contextEntity);
+
+        setParameterContext("root", createdContextEntity);
+        waitForValidProcessor(processorId);
+
+        final ParameterEntity fooNull = createParameterEntity("foo", null, 
false, null);
+        
createdContextEntity.getComponent().setParameters(Collections.singleton(fooNull));
+        
getNifiClient().getParamContextClient().updateParamContext(createdContextEntity);
+        // Should become invalid because property is required and has no 
default
+        waitForInvalidProcessor(processorId);
+    }
+
+    @Test
+    public void testParametersReferencingEL() throws NiFiClientException, 
IOException, InterruptedException {
+        final ProcessorEntity generate = 
getClientUtil().createProcessor("GenerateFlowFile");
+        getClientUtil().updateProcessorProperties(generate, 
Collections.singletonMap("a", "1"));
+        getClientUtil().updateProcessorSchedulingPeriod(generate, "10 min");
+
+        final ProcessorEntity evaluate = 
getClientUtil().createProcessor("EvaluatePropertiesWithDifferentELScopes");
+        final Map<String, String> evaluateProperties = new HashMap<>();
+        evaluateProperties.put("FlowFile Context", "#{A}");
+        evaluateProperties.put("Variable Registry Context", "#{A Replace With 
5}");
+        evaluateProperties.put("Expression Language Not Evaluated", "#{Eleven 
A}");
+        getClientUtil().updateProcessorProperties(evaluate, 
evaluateProperties);
+
+        getClientUtil().createConnection(generate, evaluate, "success");
+        getClientUtil().setAutoTerminatedRelationships(evaluate, "success");
+
+        final Set<ParameterEntity> parameters = new HashSet<>();
+        parameters.add(createParameterEntity("A", null, false, "${a}"));
+        parameters.add(createParameterEntity("A Replace With 5", null, false, 
"${a:replaceNull(5)}"));
+        parameters.add(createParameterEntity("Eleven A", null, false, 
"11${a}"));
+        final ParameterContextEntity contextEntity = 
createParameterContextEntity(getTestName(), null, parameters);
+        final ParameterContextEntity createdContextEntity = 
getNifiClient().getParamContextClient().createParamContext(contextEntity);
+
+        setParameterContext("root", createdContextEntity);
+
+        waitForValidProcessor(generate.getId());
+        waitForValidProcessor(evaluate.getId());
+
+        getClientUtil().startProcessGroupComponents("root");
+
+        waitFor(() -> {
+            try {
+                return 
getClientUtil().getCountersAsMap(evaluate.getId()).get("flowfile") == 
getNumberOfNodes();
+            } catch (final Exception e) {
+                return false;
+            }
+        });
+
+        final Map<String, Long> counters = 
getClientUtil().getCountersAsMap(evaluate.getId());
+        assertEquals(getNumberOfNodes(), counters.get("flowfile").longValue());
+        assertEquals(5L * getNumberOfNodes(), 
counters.get("variable.registry").longValue()); // Since no value present in 
variable registry, will replace null with 5.
+        assertFalse(counters.containsKey("no.el.evaluation")); // Should not 
be evaluated
+
+        // Update parameters so that Eleven A has the value 11 without 
evaluating EL.
+        final Set<ParameterEntity> updatedParameters = new HashSet<>();
+        updatedParameters.add(createParameterEntity("A", null, false, "${a}"));
+        updatedParameters.add(createParameterEntity("A Replace With 5", null, 
false, "${a:replaceNull(5)}"));
+        updatedParameters.add(createParameterEntity("Eleven A", null, false, 
"11"));
+        final ParameterContextEntity updatedContextEntity = 
createParameterContextEntity(getTestName() + "-2", null, updatedParameters);
+        final ParameterContextEntity secondContextEntity = 
getNifiClient().getParamContextClient().createParamContext(updatedContextEntity);
+
+        // Stop process group so we can change the Parameter Context, then 
change the context and restart.
+        getClientUtil().stopProcessGroupComponents("root");
+        setParameterContext("root", secondContextEntity);
+        getClientUtil().startProcessGroupComponents("root");
+
+        // Wait for the 'no.el.evaluation' counter to be set
+        waitFor(() -> {
+            try {
+                return 
getClientUtil().getCountersAsMap(evaluate.getId()).get("no.el.evaluation") == 
11 * getNumberOfNodes();
+            } catch (final Exception e) {
+                return false;
+            }
+        });
+    }
+
+    @Test
+    public void testParameterWithOptionalProperty() throws 
NiFiClientException, IOException, InterruptedException {
+        final ProcessorEntity generate = 
getClientUtil().createProcessor("GenerateFlowFile");
+        getClientUtil().updateProcessorSchedulingPeriod(generate, "10 min");
+
+        final Map<String, String> generateProperties = new HashMap<>();
+        generateProperties.put("Text", "#{Text}");
+        generateProperties.put("File Size", "1 KB");
+        getClientUtil().updateProcessorProperties(generate, 
generateProperties);
+
+        final ProcessorEntity writeFile = 
getClientUtil().createProcessor("WriteToFile");
+        final File file = new 
File("target/testParameterWithOptionalProperty.txt");
+        getClientUtil().updateProcessorProperties(writeFile, 
Collections.singletonMap("Filename", file.getAbsolutePath()));
+
+        getClientUtil().createConnection(generate, writeFile, "success");
+        getClientUtil().setAutoTerminatedRelationships(writeFile, new 
HashSet<>(Arrays.asList("success", "failure")));
+
+        final Set<ParameterEntity> parameters = new HashSet<>();
+        final ParameterContextEntity contextEntity = 
createParameterContextEntity(getTestName(), null, parameters);
+        final ParameterContextEntity createdContextEntity = 
getNifiClient().getParamContextClient().createParamContext(contextEntity);
+
+        setParameterContext("root", createdContextEntity);
+
+        // Processor should be invalid because it references a Parameter 
(Text) that does not exist
+        waitForInvalidProcessor(generate.getId());
+
+        // Update the Parameter Context to add new parameter but with null 
value.
+        final ParameterEntity nullText = createParameterEntity("Text", "Text", 
false, null);
+        createdContextEntity.getComponent().getParameters().add(nullText);
+        
getNifiClient().getParamContextClient().updateParamContext(createdContextEntity);
+
+        waitForValidProcessor(generate.getId());
+
+        getClientUtil().startProcessGroupComponents("root");
+
+        // Ensure that the file is written with a file size of 1 KB
+        waitFor(() -> {
+            try {
+                return file.exists() && file.length() == 1024;
+            } catch (final Exception e) {
+                return false;
+            }
+        });
+
+        // Update Parameter to have a specific value
+        createdContextEntity.getComponent().getParameters().remove(nullText);
+        final String customText = "Some Custom Text";
+        
createdContextEntity.getComponent().getParameters().add(createParameterEntity("Text",
 "Text", false, customText));
+
+        
getNifiClient().getParamContextClient().updateParamContext(createdContextEntity);
+
+        // Wait for file to be written out
+        waitFor(() -> {
+            try {
+                final boolean correctSize = file.exists() && file.length() == 
customText.length();
+                if (!correctSize) {
+                    return false;
+                }
+
+                final List<String> lines = Files.readAllLines(file.toPath());
+                if (lines.size() != 1) {
+                    return false;
+                }
+
+                return customText.equals(lines.get(0));
+            } catch (final Exception e) {
+                return false;
+            }
+        });
+
+    }
+
+    @Test
     public void testProcessorStartedAfterLongValidationPeriod() throws 
NiFiClientException, IOException, InterruptedException {
         final ParameterContextEntity createdContextEntity = 
createParameterContext("sleep", "6 secs");
 

Reply via email to