Repository: nifi
Updated Branches:
  refs/heads/master a6f91a197 -> 4e7a856f7


NIFI-5885 ArrayOutOfBoundsException at EL 'or' and 'and' functions

EL 'or' and 'and' functions can be called multiple times within the same 
context using the same evaluator instance.
That happens if their subject is derived from an IteratingEvaluator such as 
'anyDelineatedValues'.

And if the right hand side expression for such 'or' and 'and' contains another 
IteratingEvaluator,
then it can be evaluated more than the number of its candidates, ultimately an 
ArrayOutOfBoundsException is thrown.

This commit makes Or/AndEvaluator caching its right hand side result to prevent 
that happens.
For 'or' and 'and' functions, the right hand side expression is independant 
from their subject boolean value.
It's enough evaluating right hand side once, because it returns the same result 
even with different subjects.

Signed-off-by: Pierre Villard <pierre.villard...@gmail.com>

This closes #3212.


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/4e7a856f
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/4e7a856f
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/4e7a856f

Branch: refs/heads/master
Commit: 4e7a856f77fb8b7fbe1067ce667d8dff93dbd271
Parents: a6f91a1
Author: Koji Kawamura <ijokaruma...@apache.org>
Authored: Mon Dec 10 14:31:16 2018 +0900
Committer: Pierre Villard <pierre.villard...@gmail.com>
Committed: Mon Dec 10 11:47:58 2018 +0100

----------------------------------------------------------------------
 .../evaluation/functions/AndEvaluator.java      | 12 ++++++-
 .../evaluation/functions/OrEvaluator.java       | 14 ++++++--
 .../expression/language/TestQuery.java          | 36 ++++++++++++++++++++
 3 files changed, 59 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/4e7a856f/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/AndEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/AndEvaluator.java
 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/AndEvaluator.java
index 232fc26..adc41da 100644
--- 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/AndEvaluator.java
+++ 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/AndEvaluator.java
@@ -27,6 +27,7 @@ public class AndEvaluator extends BooleanEvaluator {
 
     private final Evaluator<Boolean> subjectEvaluator;
     private final Evaluator<Boolean> rhsEvaluator;
+    private BooleanQueryResult rhsResult;
 
     public AndEvaluator(final Evaluator<Boolean> subjectEvaluator, final 
Evaluator<Boolean> rhsEvaluator) {
         this.subjectEvaluator = subjectEvaluator;
@@ -44,9 +45,18 @@ public class AndEvaluator extends BooleanEvaluator {
             return new BooleanQueryResult(false);
         }
 
+        // Returning previously evaluated result.
+        // The same AndEvaluator can be evaluated multiple times if 
subjectEvaluator is IteratingEvaluator.
+        // In that case, it's enough to evaluate the right hand side.
+        if (rhsResult != null) {
+            return rhsResult;
+        }
+
         final QueryResult<Boolean> rhsValue = 
rhsEvaluator.evaluate(attributes);
         if (rhsValue == null) {
-            return new BooleanQueryResult(false);
+            rhsResult = new BooleanQueryResult(false);
+        } else {
+            rhsResult = new BooleanQueryResult(rhsValue.getValue());
         }
 
         return new BooleanQueryResult(rhsValue.getValue());

http://git-wip-us.apache.org/repos/asf/nifi/blob/4e7a856f/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/OrEvaluator.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/OrEvaluator.java
 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/OrEvaluator.java
index 719fa11..9c63c27 100644
--- 
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/OrEvaluator.java
+++ 
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/OrEvaluator.java
@@ -27,6 +27,7 @@ public class OrEvaluator extends BooleanEvaluator {
 
     private final Evaluator<Boolean> subjectEvaluator;
     private final Evaluator<Boolean> rhsEvaluator;
+    private BooleanQueryResult rhsResult;
 
     public OrEvaluator(final Evaluator<Boolean> subjectEvaluator, final 
Evaluator<Boolean> rhsEvaluator) {
         this.subjectEvaluator = subjectEvaluator;
@@ -44,12 +45,21 @@ public class OrEvaluator extends BooleanEvaluator {
             return new BooleanQueryResult(true);
         }
 
+        // Returning previously evaluated result.
+        // The same OrEvaluator can be evaluated multiple times if 
subjectEvaluator is IteratingEvaluator.
+        // In that case, it's enough to evaluate the right hand side.
+        if (rhsResult != null) {
+            return rhsResult;
+        }
+
         final QueryResult<Boolean> rhsValue = 
rhsEvaluator.evaluate(attributes);
         if (rhsValue == null) {
-            return new BooleanQueryResult(false);
+            rhsResult = new BooleanQueryResult(false);
+        } else {
+            rhsResult = new BooleanQueryResult(rhsValue.getValue());
         }
 
-        return new BooleanQueryResult(rhsValue.getValue());
+        return rhsResult;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/4e7a856f/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
 
b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
index 2a43bdc..5b36813 100644
--- 
a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
+++ 
b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
@@ -996,6 +996,42 @@ public class TestQuery {
     }
 
     @Test
+    public void testNestedAnyDelineatedValueOr() {
+        final Map<String, String> attributes = new HashMap<>();
+        attributes.put("abc", "a,b,c");
+        attributes.put("xyz", "x");
+
+        // Assert each part separately.
+        assertEquals("true", 
Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):equals('c')}",
+                attributes, null));
+        assertEquals("false", 
Query.evaluateExpressions("${anyDelineatedValue('${xyz}', ','):equals('z')}",
+                attributes, null));
+
+        // Combine them with 'or'.
+        assertEquals("true", Query.evaluateExpressions(
+                "${anyDelineatedValue('${abc}', 
','):equals('c'):or(${anyDelineatedValue('${xyz}', ','):equals('z')})}",
+                attributes, null));
+    }
+
+    @Test
+    public void testNestedAnyDelineatedValueAnd() {
+        final Map<String, String> attributes = new HashMap<>();
+        attributes.put("abc", "2,0,1,3");
+        attributes.put("xyz", "x,y,z");
+
+        // Assert each part separately.
+        assertEquals("true", 
Query.evaluateExpressions("${anyDelineatedValue('${abc}', ','):gt('2')}",
+                attributes, null));
+        assertEquals("true", 
Query.evaluateExpressions("${anyDelineatedValue('${xyz}', ','):equals('z')}",
+                attributes, null));
+
+        // Combine them with 'and'.
+        assertEquals("true", Query.evaluateExpressions(
+                "${anyDelineatedValue('${abc}', 
','):gt('2'):and(${anyDelineatedValue('${xyz}', ','):equals('z')})}",
+                attributes, null));
+    }
+
+    @Test
     public void testAllDelineatedValues() {
         final Map<String, String> attributes = new HashMap<>();
         attributes.put("abc", "a,b,c");

Reply via email to