mirror of https://github.com/apache/nifi.git
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.fr@gmail.com> This closes #3212.
This commit is contained in:
parent
a6f91a1975
commit
4e7a856f77
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
final QueryResult<Boolean> rhsValue = rhsEvaluator.evaluate(attributes);
|
||||
if (rhsValue == null) {
|
||||
return new BooleanQueryResult(false);
|
||||
// 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;
|
||||
}
|
||||
|
||||
return new BooleanQueryResult(rhsValue.getValue());
|
||||
final QueryResult<Boolean> rhsValue = rhsEvaluator.evaluate(attributes);
|
||||
if (rhsValue == null) {
|
||||
rhsResult = new BooleanQueryResult(false);
|
||||
} else {
|
||||
rhsResult = new BooleanQueryResult(rhsValue.getValue());
|
||||
}
|
||||
|
||||
return rhsResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -995,6 +995,42 @@ public class TestQuery {
|
|||
verifyEquals("${anyDelineatedValue(${abc}, ','):equals('d')}", attributes, false);
|
||||
}
|
||||
|
||||
@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<>();
|
||||
|
|
Loading…
Reference in New Issue