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");