You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2026/04/01 16:47:25 UTC
(nifi) branch main updated: NIFI-15739 Added Expression Language validation to ExecuteGroovyScript dynamic properties (#11059)
This is an automated email from the ASF dual-hosted git repository.
exceptionfactory 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 1ef94fc2413 NIFI-15739 Added Expression Language validation to ExecuteGroovyScript dynamic properties (#11059)
1ef94fc2413 is described below
commit 1ef94fc2413bdd74549779cbf121e47a8554ff25
Author: dan-s1 <ds...@gmail.com>
AuthorDate: Wed Apr 1 12:47:18 2026 -0400
NIFI-15739 Added Expression Language validation to ExecuteGroovyScript dynamic properties (#11059)
- Added session.get() in the onTrigger method to ensure there is a FlowFile to send to the failure relationship
Signed-off-by: David Handermann <ex...@apache.org>
---
.../processors/groovyx/ExecuteGroovyScript.java | 21 +++++++++----
.../groovyx/flow/ProcessSessionWrap.java | 2 +-
.../groovyx/ExecuteGroovyScriptTest.java | 35 +++++++++++++++++-----
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java
index d3686e62d27..489f4d62e37 100644
--- a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java
+++ b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScript.java
@@ -40,6 +40,7 @@ import org.apache.nifi.components.state.Scope;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.dbcp.DBCPService;
import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.migration.PropertyConfiguration;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
@@ -114,7 +115,8 @@ public class ExecuteGroovyScript extends AbstractProcessor {
.expressionLanguageSupported(ExpressionLanguageScope.NONE)
.build();
- public static final String[] VALID_FAIL_STRATEGY = {"rollback", "transfer to failure"};
+ static final String ROLLBACK = "rollback";
+ static final String TRANSFER_TO_FAILURE = "transfer to failure";
public static final PropertyDescriptor FAIL_STRATEGY = new PropertyDescriptor.Builder()
.name("Failure Strategy")
.description("What to do with unhandled exceptions. If you want to manage exception by code then keep the default value `rollback`."
@@ -124,8 +126,8 @@ public class ExecuteGroovyScript extends AbstractProcessor {
+ " If the processor has no incoming connections then this parameter has no effect."
)
.required(true).expressionLanguageSupported(ExpressionLanguageScope.NONE)
- .allowableValues(VALID_FAIL_STRATEGY)
- .defaultValue(VALID_FAIL_STRATEGY[0])
+ .allowableValues(ROLLBACK, TRANSFER_TO_FAILURE)
+ .defaultValue(ROLLBACK)
.build();
public static final PropertyDescriptor ADD_CLASSPATH = new PropertyDescriptor.Builder()
@@ -444,10 +446,18 @@ public class ExecuteGroovyScript extends AbstractProcessor {
@Override
public void onTrigger(final ProcessContext context, final ProcessSession processSession) throws ProcessException {
- boolean toFailureOnError = VALID_FAIL_STRATEGY[1].equals(context.getProperty(FAIL_STRATEGY).getValue());
+ boolean toFailureOnError = TRANSFER_TO_FAILURE.equals(context.getProperty(FAIL_STRATEGY).getValue());
//create wrapped session to control list of newly created and files got from this session.
//so transfer original input to failure will be possible
GroovyProcessSessionWrap session = new GroovyProcessSessionWrap(processSession, toFailureOnError);
+ if (toFailureOnError) {
+ // FlowFile must be read otherwise if there is a failure before the script is executed, a
+ // never ending loop occurs since the GroovyProcessSessionWrap has nothing to send to the failure relationship.
+ FlowFile flowFile = session.get();
+ if (flowFile == null) {
+ return;
+ }
+ }
Map<String, Object> ctl = new AccessMap("CTL");
Map<String, Object> sql = new AccessMap("SQL");
@@ -571,7 +581,8 @@ public class ExecuteGroovyScript extends AbstractProcessor {
return new PropertyDescriptor.Builder()
.name(propertyDescriptorName)
.required(false)
- .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .addValidator(StandardValidators.NON_EMPTY_EL_VALIDATOR)
+ .addValidator(StandardValidators.ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.ENVIRONMENT)
.dynamic(true)
.build();
diff --git a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/flow/ProcessSessionWrap.java b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/flow/ProcessSessionWrap.java
index 9f291f6b261..2cde23f1590 100644
--- a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/flow/ProcessSessionWrap.java
+++ b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/main/java/org/apache/nifi/processors/groovyx/flow/ProcessSessionWrap.java
@@ -224,7 +224,7 @@ public abstract class ProcessSessionWrap implements ProcessSession {
session.transfer(f);
}
}
- session.commit();
+ session.commitAsync();
onClear();
}
/*============================================= NATIVE METHODS ================================================*/
diff --git a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/test/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScriptTest.java b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/test/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScriptTest.java
index 897f286588a..b7eee39ea80 100644
--- a/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/test/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScriptTest.java
+++ b/nifi-extension-bundles/nifi-groovyx-bundle/nifi-groovyx-processors/src/test/java/org/apache/nifi/processors/groovyx/ExecuteGroovyScriptTest.java
@@ -23,6 +23,7 @@ import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.embedded.database.EmbeddedDatabaseConnectionService;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.groovyx.flow.ProcessSessionWrap;
import org.apache.nifi.serialization.RecordSetWriterFactory;
import org.apache.nifi.serialization.record.MockRecordParser;
import org.apache.nifi.serialization.record.MockRecordWriter;
@@ -142,7 +143,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void testReadFlowFileContentAndStoreInFlowFileAttribute() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, "def flowFile = session.get(); if(!flowFile)return; flowFile.testAttr = flowFile.read().getText('UTF-8'); REL_SUCCESS << flowFile;");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.enqueue("test content".getBytes(StandardCharsets.UTF_8));
@@ -187,7 +187,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_onTrigger_groovy() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "test_onTrigger.groovy");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.enqueue("test content".getBytes(StandardCharsets.UTF_8));
@@ -200,7 +199,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_onTriggerX_groovy() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "test_onTriggerX.groovy");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.enqueue("test content".getBytes(StandardCharsets.UTF_8));
@@ -213,7 +211,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_onTrigger_changeContent_groovy() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "test_onTrigger_changeContent.groovy");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.enqueue(TEST_CSV_DATA.getBytes(StandardCharsets.UTF_8));
@@ -229,7 +226,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_onTrigger_changeContentX_groovy() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "test_onTrigger_changeContentX.groovy");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.enqueue(TEST_CSV_DATA.getBytes(StandardCharsets.UTF_8));
@@ -245,7 +241,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_no_input_groovy() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "test_no_input.groovy");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
runner.run();
runner.assertAllFlowFilesTransferred(ExecuteGroovyScript.REL_SUCCESS.getName(), 1);
@@ -369,7 +364,6 @@ public class ExecuteGroovyScriptTest {
@Test
public void test_filter_01() {
runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, "def ff = session.get{it.FILTER=='3'}; if(!ff)return; REL_SUCCESS << ff;");
- //runner.setProperty(proc.FAIL_STRATEGY, "rollback");
runner.assertValid();
@@ -566,6 +560,33 @@ public class ExecuteGroovyScriptTest {
runner.run();
}
+ @Test
+ void invalidExpressionLanguageInDynamicProperty() {
+ runner.setProperty("myProperty", "${myparam:isempty()}");
+ runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, "assert true == true");
+ runner.setProperty(ExecuteGroovyScript.FAIL_STRATEGY, ExecuteGroovyScript.TRANSFER_TO_FAILURE);
+ runner.assertNotValid();
+ }
+
+ @Test
+ void validExpressionLanguageInDynamicPropertyWithVariableValueWhichCannotBeUsedWithELExpression() {
+ runner.setProperty("myProperty", "${test:toDate('yyyy-MM-dd')}");
+ runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, "assert true == true");
+ runner.setProperty(ExecuteGroovyScript.FAIL_STRATEGY, ExecuteGroovyScript.TRANSFER_TO_FAILURE);
+ runner.assertValid();
+
+ runner.setEnvironmentVariableValue("test", "cannot be converted to a date");
+ runner.enqueue("test content".getBytes(StandardCharsets.UTF_8));
+
+ runner.run();
+
+ runner.assertAllFlowFilesTransferred(ExecuteGroovyScript.REL_FAILURE.getName(), 1);
+ final MockFlowFile flowFile = runner.getFlowFilesForRelationship(ExecuteGroovyScript.REL_FAILURE).getFirst();
+ flowFile.assertAttributeExists(ProcessSessionWrap.ERROR_MESSAGE);
+ flowFile.assertAttributeExists(ProcessSessionWrap.ERROR_STACKTRACE);
+ assertTrue(flowFile.getAttribute(ProcessSessionWrap.ERROR_MESSAGE).contains("IllegalAttributeException"));
+ }
+
@Test
void testMigrateProperties() {
final Map<String, String> expectedRenamed = Map.ofEntries(