Repository: nifi Updated Branches: refs/heads/master d126743d9 -> 72a5e6bfb
NIFI-4407: Updated Expression Language Guide to provide details about how escaping $ is accomplished, and when the $ character should and should not be escaped. Fixed bug in the Query compiler that mistakenly would blindly replace 484 with $ even when the 484 did not precede an Expression. Added additional test cases. Signed-off-by: Pierre Villard <pierre.villard...@gmail.com> This closes #2898. Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/72a5e6bf Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/72a5e6bf Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/72a5e6bf Branch: refs/heads/master Commit: 72a5e6bfba1597f6174223f3afc85de478d49898 Parents: d126743 Author: Mark Payne <marka...@hotmail.com> Authored: Mon Jul 16 13:27:46 2018 -0400 Committer: Pierre Villard <pierre.villard...@gmail.com> Committed: Wed Jul 18 10:52:13 2018 +0200 ---------------------------------------------------------------------- .../attribute/expression/language/Query.java | 74 ++++-- .../language/compile/ExpressionCompiler.java | 230 +++++++++++-------- .../expression/language/TestQuery.java | 83 ++++--- .../asciidoc/expression-language-guide.adoc | 44 +++- 4 files changed, 279 insertions(+), 152 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/72a5e6bf/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java ---------------------------------------------------------------------- diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java index 8f549a5..e701341 100644 --- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java +++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java @@ -16,20 +16,21 @@ */ package org.apache.nifi.attribute.expression.language; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - import org.antlr.runtime.tree.Tree; 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.QueryResult; +import org.apache.nifi.attribute.expression.language.evaluation.selection.AttributeEvaluator; import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException; import org.apache.nifi.expression.AttributeExpression.ResultType; import org.apache.nifi.expression.AttributeValueDecorator; import org.apache.nifi.processor.exception.ProcessException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Class used for creating and evaluating NiFi Expression Language. Once a Query * has been created, it may be evaluated using the evaluate methods exactly @@ -206,8 +207,7 @@ public class Query { } final String value = evaluated.toString(); - final String escaped = value.replace("$$", "$"); - return decorator == null ? escaped : decorator.decorate(escaped); + return decorator == null ? value : decorator.decorate(value); } static String evaluateExpressions(final String rawValue, Map<String, String> expressionMap, final AttributeValueDecorator decorator, final Map<String, String> stateVariables) @@ -239,6 +239,42 @@ public class Query { return new Query(text, tree, compiler.buildEvaluator(tree)); } + private static String unescapeLeadingDollarSigns(final String value) { + final int index = value.indexOf("{"); + if (index < 0) { + return value.replace("$$", "$"); + } else { + final String prefix = value.substring(0, index); + return prefix.replace("$$", "$") + value.substring(index); + } + } + + private static String unescapeTrailingDollarSigns(final String value, final boolean escapeIfAllDollars) { + if (!value.endsWith("$")) { + return value; + } + + // count number of $$ at end of string + int dollars = 0; + for (int i=value.length()-1; i >= 0; i--) { + final char c = value.charAt(i); + if (c == '$') { + dollars++; + } else { + break; + } + } + + // If the given argument consists solely of $ characters, then we + if (dollars == value.length() && !escapeIfAllDollars) { + return value; + } + + final int charsToRemove = dollars / 2; + final int newLength = value.length() - charsToRemove; + return value.substring(0, newLength); + } + public static PreparedQuery prepare(final String query) throws AttributeExpressionLanguageParsingException { if (query == null) { @@ -248,7 +284,11 @@ public class Query { final List<Range> ranges = extractExpressionRanges(query); if (ranges.isEmpty()) { - return new EmptyPreparedQuery(query.replace("$$", "$")); + // While in the other cases below, we are simply replacing "$$" with "$", we have to do this + // a bit differently. We want to treat $$ as an escaped $ only if it immediately precedes the + // start of an Expression, which is the case below. Here, we did not detect the start of an Expression + // and as such as must use the #unescape method instead of a simple replace() function. + return new EmptyPreparedQuery(unescape(query)); } final ExpressionCompiler compiler = new ExpressionCompiler(); @@ -258,14 +298,22 @@ public class Query { int lastIndex = 0; for (final Range range : ranges) { + final String treeText = unescapeLeadingDollarSigns(query.substring(range.getStart(), range.getEnd() + 1)); + final CompiledExpression compiledExpression = compiler.compile(treeText); + if (range.getStart() > lastIndex) { - final String substring = query.substring(lastIndex, range.getStart()).replace("$$", "$"); + String substring = unescapeLeadingDollarSigns(query.substring(lastIndex, range.getStart())); + + // If this string literal evaluator immediately precedes an Attribute Reference, then we need to consider the String Literal to be + // Escaping if it ends with $$'s, otherwise not. We also want to avoid un-escaping if the expression consists solely of $$, because + // those would have been addressed by the previous #unescapeLeadingDollarSigns() call already. + if (compiledExpression.getRootEvaluator() instanceof AttributeEvaluator) { + substring = unescapeTrailingDollarSigns(substring, false); + } + expressions.add(new StringLiteralExpression(substring)); - lastIndex = range.getEnd() + 1; } - final String treeText = query.substring(range.getStart(), range.getEnd() + 1).replace("$$", "$"); - final CompiledExpression compiledExpression = compiler.compile(treeText); expressions.add(compiledExpression); lastIndex = range.getEnd() + 1; @@ -273,7 +321,7 @@ public class Query { final Range lastRange = ranges.get(ranges.size() - 1); if (lastRange.getEnd() + 1 < query.length()) { - final String treeText = query.substring(lastRange.getEnd() + 1).replace("$$", "$"); + final String treeText = unescapeLeadingDollarSigns(query.substring(lastRange.getEnd() + 1)); expressions.add(new StringLiteralExpression(treeText)); } http://git-wip-us.apache.org/repos/asf/nifi/blob/72a5e6bf/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java ---------------------------------------------------------------------- diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java index 622becd..4a4a204 100644 --- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java +++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java @@ -17,104 +17,6 @@ package org.apache.nifi.attribute.expression.language.compile; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_ATTRIBUTES; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_DELINEATED_VALUES; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_MATCHING_ATTRIBUTES; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.AND; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_ATTRIBUTE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_DELINEATED_VALUE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_MATCHING_ATTRIBUTE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.APPEND; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTRIBUTE_REFERENCE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTR_NAME; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.BASE64_DECODE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.BASE64_ENCODE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.CONTAINS; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.COUNT; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.DECIMAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.DIVIDE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ENDS_WITH; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS_IGNORE_CASE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_CSV; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_HTML3; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_HTML4; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_JSON; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_XML; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EXPRESSION; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FALSE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FIND; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FORMAT; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FROM_RADIX; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_DELIMITED_FIELD; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_STATE_VALUE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN_OR_EQUAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.HOSTNAME; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IF_ELSE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IN; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.INDEX_OF; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IP; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_EMPTY; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_NULL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JOIN; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LAST_INDEX_OF; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LENGTH; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN_OR_EQUAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MATCHES; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MATH; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MINUS; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MOD; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTIPLY; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTI_ATTRIBUTE_REFERENCE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NEXT_INT; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT_NULL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOW; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.OR; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PLUS; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PREPEND; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.RANDOM; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_ALL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_EMPTY; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_FIRST; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_NULL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STARTS_WITH; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STRING_LITERAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER_LAST; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE_LAST; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DATE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DECIMAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LITERAL; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LOWER; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_NUMBER; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_RADIX; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_STRING; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_UPPER; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRIM; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRUE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_CSV; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_HTML3; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_HTML4; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_JSON; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_XML; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_DECODE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_ENCODE; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UUID; -import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.WHOLE_NUMBER; - -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; @@ -219,6 +121,104 @@ import org.apache.nifi.attribute.expression.language.exception.AttributeExpressi import org.apache.nifi.expression.AttributeExpression.ResultType; import org.apache.nifi.flowfile.FlowFile; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_ATTRIBUTES; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_DELINEATED_VALUES; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_MATCHING_ATTRIBUTES; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.AND; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_ATTRIBUTE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_DELINEATED_VALUE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ANY_MATCHING_ATTRIBUTE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.APPEND; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTRIBUTE_REFERENCE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ATTR_NAME; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.BASE64_DECODE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.BASE64_ENCODE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.CONTAINS; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.COUNT; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.DECIMAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.DIVIDE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ENDS_WITH; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EQUALS_IGNORE_CASE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_CSV; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_HTML3; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_HTML4; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_JSON; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ESCAPE_XML; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.EXPRESSION; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FALSE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FIND; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FORMAT; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FROM_RADIX; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_DELIMITED_FIELD; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_STATE_VALUE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GREATER_THAN_OR_EQUAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.HOSTNAME; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IF_ELSE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IN; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.INDEX_OF; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IP; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_EMPTY; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.IS_NULL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JOIN; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.JSON_PATH; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LAST_INDEX_OF; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LENGTH; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.LESS_THAN_OR_EQUAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MATCHES; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MATH; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MINUS; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MOD; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTIPLY; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.MULTI_ATTRIBUTE_REFERENCE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NEXT_INT; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOT_NULL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.NOW; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.OR; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PLUS; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.PREPEND; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.RANDOM; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_ALL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_EMPTY; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_FIRST; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.REPLACE_NULL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STARTS_WITH; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.STRING_LITERAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_AFTER_LAST; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.SUBSTRING_BEFORE_LAST; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DATE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DECIMAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LITERAL; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LOWER; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_NUMBER; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_RADIX; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_STRING; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_UPPER; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRIM; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TRUE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_CSV; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_HTML3; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_HTML4; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_JSON; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UNESCAPE_XML; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_DECODE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.URL_ENCODE; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.UUID; +import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.WHOLE_NUMBER; + public class ExpressionCompiler { private final Set<Evaluator<?>> evaluators = new HashSet<>(); @@ -1066,6 +1066,26 @@ public class ExpressionCompiler { return evaluator; } + private String unescapeTrailingDollarSigns(final String value) { + if (!value.endsWith("$")) { + return value; + } + + // count number of $$ at end of string + int dollars = 0; + for (int i=value.length()-1; i >= 0; i--) { + final char c = value.charAt(i); + if (c == '$') { + dollars++; + } else { + break; + } + } + + final int charsToRemove = dollars / 2; + final int newLength = value.length() - charsToRemove; + return value.substring(0, newLength); + } private Evaluator<String> newStringLiteralEvaluator(final String literalValue) { if (literalValue == null || literalValue.length() < 2) { @@ -1081,12 +1101,20 @@ public class ExpressionCompiler { int lastIndex = 0; for (final Range range : ranges) { + final String treeText = literalValue.substring(range.getStart(), range.getEnd() + 1); + final Evaluator<?> evaluator = buildEvaluator(compileTree(treeText)); + if (range.getStart() > lastIndex) { - evaluators.add(newStringLiteralEvaluator(literalValue.substring(lastIndex, range.getStart()))); + // If this string literal evaluator immediately precedes an Attribute Reference, then we need to consider the String Literal to be + // Escaping if it ends with $$'s, otherwise not. + if (evaluator instanceof AttributeEvaluator) { + evaluators.add(newStringLiteralEvaluator(unescapeTrailingDollarSigns(literalValue.substring(lastIndex, range.getStart())))); + } else { + evaluators.add(newStringLiteralEvaluator(literalValue.substring(lastIndex, range.getStart()))); + } } - final String treeText = literalValue.substring(range.getStart(), range.getEnd() + 1); - evaluators.add(buildEvaluator(compileTree(treeText))); + evaluators.add(evaluator); lastIndex = range.getEnd() + 1; } http://git-wip-us.apache.org/repos/asf/nifi/blob/72a5e6bf/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 6275bac..7fd8747 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 @@ -16,15 +16,19 @@ */ package org.apache.nifi.attribute.expression.language; -import static java.lang.Double.NEGATIVE_INFINITY; -import static java.lang.Double.NaN; -import static java.lang.Double.POSITIVE_INFINITY; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.antlr.runtime.tree.Tree; +import org.apache.nifi.attribute.expression.language.Query.Range; +import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult; +import org.apache.nifi.attribute.expression.language.evaluation.QueryResult; +import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException; +import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException; +import org.apache.nifi.expression.AttributeExpression.ResultType; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.registry.VariableRegistry; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; import java.io.BufferedInputStream; import java.io.IOException; @@ -40,19 +44,15 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import org.antlr.runtime.tree.Tree; -import org.apache.nifi.attribute.expression.language.Query.Range; -import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult; -import org.apache.nifi.attribute.expression.language.evaluation.QueryResult; -import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException; -import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException; -import org.apache.nifi.expression.AttributeExpression.ResultType; -import org.apache.nifi.flowfile.FlowFile; -import org.apache.nifi.registry.VariableRegistry; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.Mockito; +import static java.lang.Double.NEGATIVE_INFINITY; +import static java.lang.Double.NaN; +import static java.lang.Double.POSITIVE_INFINITY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class TestQuery { @@ -78,6 +78,8 @@ public class TestQuery { public void testPrepareWithEscapeChar() { final Map<String, String> variables = Collections.singletonMap("foo", "bar"); + assertEquals("bar${foo}$bar", Query.prepare("${foo}$${foo}$$${foo}").evaluateExpressions(variables, null)); + final PreparedQuery onlyEscapedQuery = Query.prepare("$${foo}"); final String onlyEscapedEvaluated = onlyEscapedQuery.evaluateExpressions(variables, null); assertEquals("${foo}", onlyEscapedEvaluated); @@ -85,8 +87,6 @@ public class TestQuery { 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) { @@ -167,14 +167,33 @@ public class TestQuery { @Test public void testEscape() { final Map<String, String> attributes = new HashMap<>(); - attributes.put("attr", "My Value"); - attributes.put("${xx}", "hello"); - - assertEquals("My Value", evaluateQueryForEscape("${attr}", attributes)); - assertEquals("${attr}", evaluateQueryForEscape("$${attr}", attributes)); - assertEquals("$My Value", evaluateQueryForEscape("$$${attr}", attributes)); - assertEquals("$${attr}", evaluateQueryForEscape("$$$${attr}", attributes)); - assertEquals("$$My Value", evaluateQueryForEscape("$$$$${attr}", attributes)); + attributes.put("abc", "xyz"); + attributes.put("xyz", "hello"); + attributes.put("$xyz", "good-bye"); + attributes.put("${abc}", "good-bye"); + + assertEquals("$$xyz", evaluateQueryForEscape("$$$$${abc}", attributes)); + assertEquals("xyz", evaluateQueryForEscape("${abc}", attributes)); + assertEquals("${abc}", evaluateQueryForEscape("$${abc}", attributes)); + assertEquals("$xyz", evaluateQueryForEscape("$$${abc}", attributes)); + assertEquals("$${abc}", evaluateQueryForEscape("$$$${abc}", attributes)); + + assertEquals( "Unescaped $$${5 because no closing brace", evaluateQueryForEscape("Unescaped $$${5 because no closing brace", attributes)); + assertEquals( "Unescaped $ because no closing brace", evaluateQueryForEscape("Unescaped $$${'5'} because no closing brace", attributes)); + + assertEquals("I owe you $5", evaluateQueryForEscape("I owe you $5", attributes)); + assertEquals("You owe me $$5 too", evaluateQueryForEscape("You owe me $$5 too", attributes)); + assertEquals("Unescaped $$${5 because no closing brace", evaluateQueryForEscape("Unescaped $$${5 because no closing brace", attributes)); + assertEquals("xyz owes me $5", evaluateQueryForEscape("${abc} owes me $5", attributes)); + assertEquals("xyz owes me ${5", evaluateQueryForEscape("${abc} owes me ${5", attributes)); + assertEquals("xyz owes me ", evaluateQueryForEscape("${abc} owes me ${'5'}", attributes)); + assertEquals("xyz owes me $", evaluateQueryForEscape("${abc} owes me $$${'5'}", attributes)); + + assertEquals("SNAP$$$$$$$$$", evaluateQueryForEscape("${literal('SNAP$$$$$$$$$')}", attributes)); + assertEquals("SNAP$$", evaluateQueryForEscape("${literal('SNAP$$')}", attributes)); + assertEquals("hello", evaluateQueryForEscape("${${abc}}", attributes)); + assertEquals("good-bye", evaluateQueryForEscape("${'$$${abc}'}", attributes)); + assertEquals("good-bye", evaluateQueryForEscape("${'$xyz'}", attributes)); } @Test http://git-wip-us.apache.org/repos/asf/nifi/blob/72a5e6bf/nifi-docs/src/main/asciidoc/expression-language-guide.adoc ---------------------------------------------------------------------- diff --git a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc index 56906d8..a58e8f6 100644 --- a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc +++ b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc @@ -142,24 +142,56 @@ a Property supports the Expression Language is determined by the developer of th the Processor is written. However, the application strives to clearly illustrate for each Property whether or not the Expression Language is supported. -In the application, when configuring a Processor property, the User Interface provides an Information +In the application, when configuring a component property, the User Interface provides an Information icon ( image:iconInfo.png["Info"] ) next to the name of the Property. Hovering over this icon with the mouse will provide a tooltip that provides helpful information about the Property. This information includes a description of the Property, the default value (if any), historically configured values (if any), and the evaluation scope of this -property for expression language. There are three values and the evaluation scope of the expression +property for expression language. There are three values and the evaluation scope of the expression language is hierarchical: NONE -> VARIABLE_REGISTRY -> FLOWFILE_ATTRIBUTES. * NONE - expression language is not supported for this property * VARIABLE_REGISTRY is hierarchically constructed as below: -** Variables defined at process group level and then, recursively, up to the higher process group until +** Variables defined at process group level and then, recursively, up to the higher process group until the root process group. -** Variables defined in custom properties files through the nifi.variable.registry.properties property +** Variables defined in custom properties files through the nifi.variable.registry.properties property in nifi.properties file. ** Environment variables defined at JVM level and system properties. -* FLOWFILE_ATTRIBUTES - will use attributes of each individual flow file. - +* FLOWFILE_ATTRIBUTES - will use attributes of each individual flow file, as well as those variables defined +by the Variable Registry, as described above. + +[[escaping]] +=== Escaping Expression Language +There may be times when a property supports Expression Language, but the user wishes to use a literal value +that follows the same syntax as the Expression Language. For example, a user may want to configure a property +value to be the literal text `Hello ${UserName}`. In such a case, this can be accomplished by using an extra +`$` (dollar sign symbol) just before the expression to escape it (i.e., `Hello $${UserName}`). Unless the `$` +character is being used to escape an Expression, it should not be escaped. For example, the value `Hello $$User$$Name` +should not escape the `$` characters, so the literal value that will be used is `Hello $$User$$Name`. + +If more than two `$` characters are encountered sequentially before a `{`, then each pair of `$` characters will +be considered an escaping of the `$` character. The escaping will be performed from left-to-right. +To help illustrate this, consider that the variable `abc` contains the value `xyz`. Then, consider the following +table of Expressions and their corresponding evaluated values: + +.Escaping EL Examples +|======================================================================================== +| Expression | Value | Notes +| `${abc}` | `xyz` | +| `$${abc}` | `${abc}` | +| `$$${abc}` | `$xyz` | +| `$$$${abc}` | `$${abc}` | +| `$$$$${abc}` | `$$xyz` | +| `I owe you $5` | `I owe you $5` | No actual Expression is present here. +| `You owe me $$5 too` | `You owe me $$5 too` | The $ character is not escaped because it does not immediately precede an Expression. +| `Unescaped $$${5 because no closing brace` | `Unescaped $$${5 because no closing brace` | Because there is no closing brace here, there is no actual Expression and hence the $ characters are not +escaped. +| `Unescaped $$${5} because no closing brace` | <Error> | This expression is not valid because it equates to an escaped $, followed by `${5}` and the `${5}` is not a valid Expression. The number +must be escaped. +| `Unescaped $$${'5'} because no closing brace` | `Unescaped $ because no closing brace` | There is no attribute named `5` so the Expression evaluates to an empty string. The `$$` evaluates to a +single (escaped) `$` because it immediately precedes an Expression. +|======================================================================================== [[editor]] === Expression Language Editor