NIFI-5026: Refactored StandardPreparedQuery so that instead of a List of Strings that may or may not correspond to compiled expressions and a Map of String to Compiled Expression, the StandardPreparedQuery now just takes a List of Expression objects, and those Expressions can be evaluated to return the proper result

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

This closes #2592.
This commit is contained in:
Mark Payne 2018-03-28 12:45:05 -04:00 committed by Pierre Villard
parent bbbe428e7b
commit fd31c161a2
7 changed files with 116 additions and 28 deletions

View File

@ -15,14 +15,16 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.nifi.attribute.expression.language.compile; package org.apache.nifi.attribute.expression.language;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.antlr.runtime.tree.Tree; import org.antlr.runtime.tree.Tree;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator; import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.expression.AttributeValueDecorator;
public class CompiledExpression { public class CompiledExpression implements Expression {
private final Evaluator<?> rootEvaluator; private final Evaluator<?> rootEvaluator;
private final Tree tree; private final Tree tree;
private final String expression; private final String expression;
@ -50,4 +52,9 @@ public class CompiledExpression {
public Set<Evaluator<?>> getAllEvaluators() { public Set<Evaluator<?>> getAllEvaluators() {
return allEvaluators; return allEvaluators;
} }
@Override
public String evaluate(final Map<String, String> variables, final AttributeValueDecorator decorator, final Map<String, String> stateVariables) {
return Query.evaluateExpression(getTree(), expression, variables, decorator, stateVariables);
}
} }

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.attribute.expression.language;
import java.util.Map;
import org.apache.nifi.expression.AttributeValueDecorator;
public interface Expression {
/**
* Evaluates this Expression against the given variables, attribute decorator, and state variables
*
* @param variables variables to be evaluated
* @param decorator decorator to decorate variable values
* @param stateVariables state variables to include in evaluation
* @return the evaluated value
*/
String evaluate(Map<String, String> variables, AttributeValueDecorator decorator, Map<String, String> stateVariables);
}

View File

@ -17,13 +17,11 @@
package org.apache.nifi.attribute.expression.language; package org.apache.nifi.attribute.expression.language;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.antlr.runtime.tree.Tree; import org.antlr.runtime.tree.Tree;
import org.apache.nifi.attribute.expression.language.compile.CompiledExpression;
import org.apache.nifi.attribute.expression.language.compile.ExpressionCompiler; import org.apache.nifi.attribute.expression.language.compile.ExpressionCompiler;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator; import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult; import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
@ -256,32 +254,30 @@ public class Query {
final ExpressionCompiler compiler = new ExpressionCompiler(); final ExpressionCompiler compiler = new ExpressionCompiler();
try { try {
final List<String> substrings = new ArrayList<>(); final List<Expression> expressions = new ArrayList<>();
final Map<String, CompiledExpression> compiledExpressions = new HashMap<>();
int lastIndex = 0; int lastIndex = 0;
for (final Range range : ranges) { for (final Range range : ranges) {
if (range.getStart() > lastIndex) { if (range.getStart() > lastIndex) {
substrings.add(query.substring(lastIndex, range.getStart()).replace("$$", "$")); final String substring = query.substring(lastIndex, range.getStart()).replace("$$", "$");
expressions.add(new StringLiteralExpression(substring));
lastIndex = range.getEnd() + 1; lastIndex = range.getEnd() + 1;
} }
final String treeText = query.substring(range.getStart(), range.getEnd() + 1).replace("$$", "$"); final String treeText = query.substring(range.getStart(), range.getEnd() + 1).replace("$$", "$");
substrings.add(treeText);
final CompiledExpression compiledExpression = compiler.compile(treeText); final CompiledExpression compiledExpression = compiler.compile(treeText);
expressions.add(compiledExpression);
compiledExpressions.put(treeText, compiledExpression);
lastIndex = range.getEnd() + 1; lastIndex = range.getEnd() + 1;
} }
final Range lastRange = ranges.get(ranges.size() - 1); final Range lastRange = ranges.get(ranges.size() - 1);
if (lastRange.getEnd() + 1 < query.length()) { if (lastRange.getEnd() + 1 < query.length()) {
final String treeText = query.substring(lastRange.getEnd() + 1).replace("$$", "$"); final String treeText = query.substring(lastRange.getEnd() + 1).replace("$$", "$");
substrings.add(treeText); expressions.add(new StringLiteralExpression(treeText));
} }
return new StandardPreparedQuery(substrings, compiledExpressions); return new StandardPreparedQuery(expressions);
} catch (final AttributeExpressionLanguageParsingException e) { } catch (final AttributeExpressionLanguageParsingException e) {
return new InvalidPreparedQuery(query, e.getMessage()); return new InvalidPreparedQuery(query, e.getMessage());
} }

View File

@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.nifi.attribute.expression.language.compile.CompiledExpression;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator; import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.literals.StringLiteralEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.literals.StringLiteralEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.selection.AllAttributesEvaluator; import org.apache.nifi.attribute.expression.language.evaluation.selection.AllAttributesEvaluator;
@ -37,29 +36,25 @@ import org.apache.nifi.processor.exception.ProcessException;
public class StandardPreparedQuery implements PreparedQuery { public class StandardPreparedQuery implements PreparedQuery {
private final List<String> queryStrings; private final List<Expression> expressions;
private final Map<String, CompiledExpression> expressions;
private volatile VariableImpact variableImpact; private volatile VariableImpact variableImpact;
public StandardPreparedQuery(final List<String> queryStrings, final Map<String, CompiledExpression> expressions) { public StandardPreparedQuery(final List<Expression> expressions) {
this.queryStrings = queryStrings;
this.expressions = expressions; this.expressions = expressions;
} }
@Override @Override
public String evaluateExpressions(final Map<String, String> valMap, final AttributeValueDecorator decorator, final Map<String, String> stateVariables) throws ProcessException { public String evaluateExpressions(final Map<String, String> valMap, final AttributeValueDecorator decorator, final Map<String, String> stateVariables) throws ProcessException {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (final String val : queryStrings) {
final CompiledExpression expression = expressions.get(val); for (final Expression expression : expressions) {
if (expression == null) { final String evaluated = expression.evaluate(valMap, decorator, stateVariables);
sb.append(val);
} else { if (evaluated != null) {
final String evaluated = Query.evaluateExpression(expression.getTree(), val, valMap, decorator, stateVariables); sb.append(evaluated);
if (evaluated != null) {
sb.append(evaluated);
}
} }
} }
return sb.toString(); return sb.toString();
} }
@ -83,8 +78,13 @@ public class StandardPreparedQuery implements PreparedQuery {
final Set<String> variables = new HashSet<>(); final Set<String> variables = new HashSet<>();
for (final CompiledExpression expression : expressions.values()) { for (final Expression expression : expressions) {
for (final Evaluator<?> evaluator : expression.getAllEvaluators()) { if (!(expression instanceof CompiledExpression)) {
continue;
}
final CompiledExpression compiled = (CompiledExpression) expression;
for (final Evaluator<?> evaluator : compiled.getAllEvaluators()) {
if (evaluator instanceof AttributeEvaluator) { if (evaluator instanceof AttributeEvaluator) {
final AttributeEvaluator attributeEval = (AttributeEvaluator) evaluator; final AttributeEvaluator attributeEval = (AttributeEvaluator) evaluator;
final Evaluator<String> nameEval = attributeEval.getNameEvaluator(); final Evaluator<String> nameEval = attributeEval.getNameEvaluator();

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.attribute.expression.language;
import java.util.Map;
import org.apache.nifi.expression.AttributeValueDecorator;
public class StringLiteralExpression implements Expression {
private final String value;
public StringLiteralExpression(final String value) {
this.value = value;
}
@Override
public String evaluate(Map<String, String> variables, AttributeValueDecorator decorator, Map<String, String> stateVariables) {
return value;
}
}

View File

@ -119,6 +119,7 @@ import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream; import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.tree.Tree; import org.antlr.runtime.tree.Tree;
import org.apache.nifi.attribute.expression.language.CompiledExpression;
import org.apache.nifi.attribute.expression.language.Query; import org.apache.nifi.attribute.expression.language.Query;
import org.apache.nifi.attribute.expression.language.Query.Range; import org.apache.nifi.attribute.expression.language.Query.Range;
import org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionLexer; import org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionLexer;

View File

@ -74,6 +74,21 @@ public class TestQuery {
//System.out.println(Query.compile("").evaluate(null)); //System.out.println(Query.compile("").evaluate(null));
} }
@Test
public void testPrepareWithEscapeChar() {
final Map<String, String> variables = Collections.singletonMap("foo", "bar");
final PreparedQuery onlyEscapedQuery = Query.prepare("$${foo}");
final String onlyEscapedEvaluated = onlyEscapedQuery.evaluateExpressions(variables, null);
assertEquals("${foo}", onlyEscapedEvaluated);
final PreparedQuery mixedQuery = Query.prepare("${foo}$${foo}");
final String mixedEvaluated = mixedQuery.evaluateExpressions(variables, null);
assertEquals("bar${foo}", mixedEvaluated);
assertEquals("bar${foo}$bar", Query.prepare("${foo}$${foo}$$${foo}").evaluateExpressions(variables, null));
}
private void assertValid(final String query) { private void assertValid(final String query) {
try { try {
Query.compile(query); Query.compile(query);