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

pvillard 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 c43ee449e22 NIFI-15840 Added rollback in ExecuteGroovyScript to allow 
script access to FlowFile. (#11155)
c43ee449e22 is described below

commit c43ee449e22406f9ca6a53e565d4ef8271bc99aa
Author: dan-s1 <[email protected]>
AuthorDate: Thu Apr 16 17:08:31 2026 -0400

    NIFI-15840 Added rollback in ExecuteGroovyScript to allow script access to 
FlowFile. (#11155)
---
 .../processors/groovyx/ExecuteGroovyScript.java    | 14 ++++--
 .../groovyx/ExecuteGroovyScriptTest.java           | 50 +++++++++++++++++++++-
 2 files changed, 59 insertions(+), 5 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 489f4d62e37..300494152ab 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
@@ -450,10 +450,11 @@ public class ExecuteGroovyScript extends 
AbstractProcessor {
         //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);
+        FlowFile flowFile = null;
         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();
+            flowFile = session.get();
             if (flowFile == null) {
                 return;
             }
@@ -466,7 +467,7 @@ public class ExecuteGroovyScript extends AbstractProcessor {
 
         try {
             Script script = getGroovyScript(); //compilation must be moved to 
validation
-            Map bindings = script.getBinding().getVariables();
+            Map<String, Object> bindings = script.getBinding().getVariables();
 
             bindings.clear();
             Map<String, String> attributes = new HashMap<>();
@@ -510,6 +511,11 @@ public class ExecuteGroovyScript extends AbstractProcessor 
{
             bindings.put("RecordReader", recordReader);
             bindings.put("RecordWriter", recordSetWriter);
 
+            // Must perform rollback to allow the script access to the 
FlowFile.
+            if (flowFile != null) {
+                session.rollback();
+            }
+
             script.run();
             bindings.clear();
 
@@ -519,6 +525,8 @@ public class ExecuteGroovyScript extends AbstractProcessor {
             getLogger().error(t.toString(), t);
             onFailSQL(sql);
             if (toFailureOnError) {
+                // FlowFile must be retrieved in order send to the failure 
relationship as it may not have been retrieved in the script.
+                session.get();
                 //transfer all received to failure with two new attributes: 
ERROR_MESSAGE and ERROR_STACKTRACE.
                 session.revertReceivedTo(REL_FAILURE, 
StackTraceUtils.deepSanitize(t));
             } else {
@@ -590,7 +598,7 @@ public class ExecuteGroovyScript extends AbstractProcessor {
 
     /** simple HashMap with exception on access of non-existent key */
     private static class AccessMap extends HashMap<String, Object> {
-        private String parentKey;
+        private final String parentKey;
         AccessMap(String parentKey) {
             this.parentKey = parentKey;
         }
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 b7eee39ea80..d74df4362fd 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
@@ -576,7 +576,7 @@ public class ExecuteGroovyScriptTest {
         runner.assertValid();
 
         runner.setEnvironmentVariableValue("test", "cannot be converted to a 
date");
-        runner.enqueue("test content".getBytes(StandardCharsets.UTF_8));
+        runner.enqueue("test content");
 
         runner.run();
 
@@ -587,6 +587,52 @@ public class ExecuteGroovyScriptTest {
         
assertTrue(flowFile.getAttribute(ProcessSessionWrap.ERROR_MESSAGE).contains("IllegalAttributeException"));
     }
 
+    @Test
+    void testTransferToFailureStrategyWhereScriptHandlesRelationship() {
+        runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, """
+                def ff = session.get()
+                if (!ff) return
+                session.transfer(ff, REL_SUCCESS)
+                """);
+        runner.setProperty(ExecuteGroovyScript.FAIL_STRATEGY, 
ExecuteGroovyScript.TRANSFER_TO_FAILURE);
+        runner.assertValid();
+
+        runner.enqueue("test content");
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(ExecuteGroovyScript.REL_SUCCESS, 
1);
+    }
+
+    @Test
+    void testTransferToFailureStrategyWhereScriptFailsBeforeSessionGet() {
+        runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, """
+                throw new RuntimeException("fail before session.get")
+                """);
+        runner.setProperty(ExecuteGroovyScript.FAIL_STRATEGY, 
ExecuteGroovyScript.TRANSFER_TO_FAILURE);
+        runner.assertValid();
+        runner.enqueue("test content");
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(ExecuteGroovyScript.REL_FAILURE, 
1);
+    }
+
+    @Test
+    void testTransferToFailureStrategyWhereScriptFailsAfterSessionGet() {
+        runner.setProperty(ExecuteGroovyScript.SCRIPT_BODY, """
+                session.get()
+                throw new RuntimeException("fail after session.get")
+                """);
+        runner.setProperty(ExecuteGroovyScript.FAIL_STRATEGY, 
ExecuteGroovyScript.TRANSFER_TO_FAILURE);
+        runner.assertValid();
+        runner.enqueue("test content");
+
+        runner.run();
+
+        runner.assertAllFlowFilesTransferred(ExecuteGroovyScript.REL_FAILURE, 
1);
+    }
+
     @Test
     void testMigrateProperties() {
         final Map<String, String> expectedRenamed = Map.ofEntries(
@@ -611,7 +657,7 @@ public class ExecuteGroovyScriptTest {
         String expectedContent = string;
 
         if (windows) {
-            expectedContent = expectedContent.replaceAll("\n", "\r\n");
+            expectedContent = expectedContent.replace("\n", "\r\n");
         }
 
         return expectedContent;

Reply via email to