This is an automated email from the ASF dual-hosted git repository.
mthomsen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/master by this push:
new f1be730 NIFI-6782: Added repeat() String EL function
f1be730 is described below
commit f1be730c9470e56e6042be8418cb03a70845eb69
Author: Matthew Burgess <[email protected]>
AuthorDate: Thu Oct 17 15:37:14 2019 -0400
NIFI-6782: Added repeat() String EL function
NIFI-6782: Fixed intermittent unit test failure
This closes #3825
Signed-off-by: Mike Thomsen <[email protected]>
---
.../language/antlr/AttributeExpressionLexer.g | 1 +
.../language/antlr/AttributeExpressionParser.g | 2 +-
.../language/compile/ExpressionCompiler.java | 15 +++++
.../evaluation/functions/RepeatEvaluator.java | 74 ++++++++++++++++++++++
.../attribute/expression/language/TestQuery.java | 39 ++++++++++++
.../main/asciidoc/expression-language-guide.adoc | 42 ++++++++++++
6 files changed, 172 insertions(+), 1 deletion(-)
diff --git
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
index 23e59fe..1684412 100644
---
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
+++
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
@@ -182,6 +182,7 @@ JOIN : 'join';
TO_LITERAL : 'literal';
JSON_PATH : 'jsonPath';
JSON_PATH_DELETE : 'jsonPathDelete';
+REPEAT : 'repeat';
// 2 arg functions
SUBSTRING : 'substring';
diff --git
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
index 71e7ce9..b2e9039 100644
---
a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
+++
b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
@@ -79,7 +79,7 @@ oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST |
SUBSTRING_AFTER | SU
PREPEND | APPEND | STARTS_WITH | ENDS_WITH |
CONTAINS | JOIN | JSON_PATH | JSON_PATH_DELETE | FROM_RADIX) LPAREN! anyArg
RPAREN!) |
(TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE |
JSON_PATH_SET | JSON_PATH_ADD) LPAREN! anyArg COMMA! anyArg RPAREN!) |
- ((SUBSTRING | FORMAT | PAD_LEFT | PAD_RIGHT) LPAREN!
anyArg (COMMA! anyArg)? RPAREN!);
+ ((SUBSTRING | FORMAT | PAD_LEFT | PAD_RIGHT |
REPEAT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
threeArgString: ((JSON_PATH_PUT) LPAREN! anyArg COMMA! anyArg COMMA! anyArg
RPAREN!);
fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA!
anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!;
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 3f438fb..4624058 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
@@ -89,6 +89,7 @@ import
org.apache.nifi.attribute.expression.language.evaluation.functions.PadRig
import
org.apache.nifi.attribute.expression.language.evaluation.functions.PlusEvaluator;
import
org.apache.nifi.attribute.expression.language.evaluation.functions.PrependEvaluator;
import
org.apache.nifi.attribute.expression.language.evaluation.functions.RandomNumberGeneratorEvaluator;
+import
org.apache.nifi.attribute.expression.language.evaluation.functions.RepeatEvaluator;
import
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceAllEvaluator;
import
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEmptyEvaluator;
import
org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceEvaluator;
@@ -208,6 +209,7 @@ import static
org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
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.REPEAT;
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;
@@ -721,6 +723,19 @@ public class ExpressionCompiler {
throw new
AttributeExpressionLanguageParsingException("substring() function can take
either 1 or 2 arguments but cannot take " + numArgs + " arguments");
}
}
+ case REPEAT: {
+ final int numArgs = argEvaluators.size();
+ if (numArgs == 1) {
+ return addToken(new
RepeatEvaluator(toStringEvaluator(subjectEvaluator),
+ toWholeNumberEvaluator(argEvaluators.get(0),
"first argument to repeat")), "repeat");
+ } else if (numArgs == 2) {
+ return addToken(new
RepeatEvaluator(toStringEvaluator(subjectEvaluator),
+ toWholeNumberEvaluator(argEvaluators.get(0),
"first argument to repeat"),
+ toWholeNumberEvaluator(argEvaluators.get(1),
"second argument to repeat")), "repeat");
+ } else {
+ throw new
AttributeExpressionLanguageParsingException("repeat() function can take either
1 or 2 arguments but cannot take " + numArgs + " arguments");
+ }
+ }
case JOIN: {
verifyArgCount(argEvaluators, 1, "join");
return addToken(new
JoinEvaluator(toStringEvaluator(subjectEvaluator),
toStringEvaluator(argEvaluators.get(0))), "join");
diff --git
a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/RepeatEvaluator.java
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/RepeatEvaluator.java
new file mode 100644
index 0000000..d605f0d
--- /dev/null
+++
b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/RepeatEvaluator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.evaluation.functions;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+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.StringEvaluator;
+import
org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
+import
org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException;
+
+public class RepeatEvaluator extends StringEvaluator {
+
+ private final Evaluator<String> subject;
+ private final Evaluator<Long> minRepeats;
+ private final Evaluator<Long> maxRepeats;
+
+ public RepeatEvaluator(final Evaluator<String> subject, final
Evaluator<Long> minRepeats, final Evaluator<Long> maxRepeats) {
+ this.subject = subject;
+ this.minRepeats = minRepeats;
+ this.maxRepeats = maxRepeats;
+ }
+
+ public RepeatEvaluator(final Evaluator<String> subject, final
Evaluator<Long> minRepeats) {
+ this.subject = subject;
+ this.minRepeats = minRepeats;
+ this.maxRepeats = null;
+ }
+
+ @Override
+ public QueryResult<String> evaluate(final EvaluationContext
evaluationContext) {
+ final String subjectValue =
subject.evaluate(evaluationContext).getValue();
+ if (subjectValue == null) {
+ return new StringQueryResult("");
+ }
+ final int firstRepeatValue =
minRepeats.evaluate(evaluationContext).getValue().intValue();
+ if (maxRepeats == null) {
+ if (firstRepeatValue <= 0) {
+ throw new AttributeExpressionLanguageException("Number of
repeats must be > 0");
+ }
+ return new StringQueryResult(StringUtils.repeat(subjectValue,
firstRepeatValue));
+ } else {
+ if (firstRepeatValue <= 0) {
+ throw new AttributeExpressionLanguageException("Minimum number
of repeats must be > 0");
+ }
+ final int maxRepeatCount =
maxRepeats.evaluate(evaluationContext).getValue().intValue();
+ if (firstRepeatValue > maxRepeatCount) {
+ throw new AttributeExpressionLanguageException("Min repeats
must not be greater than max repeats");
+ }
+ final int randomRepeatCount = ((int) (Math.random() *
(maxRepeatCount - firstRepeatValue + 1))) + firstRepeatValue;
+ return new StringQueryResult(StringUtils.repeat(subjectValue,
randomRepeatCount));
+ }
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subject;
+ }
+}
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 3753ee8..d48129d 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
@@ -2098,6 +2098,45 @@ public class TestQuery {
verifyEmpty("${nullString:padRight(10, \"@\")}", attributes);
}
+ @Test
+ public void testRepeat() {
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("str", "abc");
+
+ verifyEquals("${not_exist:repeat(1, 2)}", attributes, "");
+ verifyEquals("${str:repeat(1, 1)}", attributes, "abc");
+
+ // Custom verify because the result could be one of multiple options
+ String multipleResultExpression = "${str:repeat(1, 3)}";
+ String multipleResultExpectedResult1 = "abc";
+ String multipleResultExpectedResult2 = "abcabc";
+ String multipleResultExpectedResult3 = "abcabcabc";
+ List<String> multipleResultExpectedResults =
Arrays.asList(multipleResultExpectedResult1, multipleResultExpectedResult2,
multipleResultExpectedResult3);
+ Query.validateExpression(multipleResultExpression, false);
+ final String actualResult =
Query.evaluateExpressions(multipleResultExpression, attributes, null, null,
ParameterLookup.EMPTY);
+ assertTrue(multipleResultExpectedResults.contains(actualResult));
+
+ verifyEquals("${str:repeat(4)}", attributes, "abcabcabcabc");
+ try {
+ verifyEquals("${str:repeat(-1)}", attributes, "");
+ fail("Should have failed on numRepeats < 0");
+ } catch(AttributeExpressionLanguageException aele) {
+ // Do nothing, it is expected
+ }
+ try {
+ verifyEquals("${str:repeat(0)}", attributes, "");
+ fail("Should have failed on numRepeats = 0");
+ } catch(AttributeExpressionLanguageException aele) {
+ // Do nothing, it is expected
+ }
+ try {
+ verifyEquals("${str:repeat(2,1)}", attributes, "");
+ fail("Should have failed on minRepeats > maxRepeats");
+ } catch(AttributeExpressionLanguageException aele) {
+ // Do nothing, it is expected
+ }
+ }
+
private void verifyEquals(final String expression, final Map<String,
String> attributes, final Object expectedResult) {
verifyEquals(expression,attributes, null, ParameterLookup.EMPTY,
expectedResult);
}
diff --git a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
index 6445aa3..70fb9d9 100644
--- a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
@@ -1123,6 +1123,48 @@ Expressions will provide the following results:
then the Expression `${query:evaluateELString()}` will return SELECT *
FROM TABLE WHERE ID = 20
+
+
+[.function]
+=== repeat
+
+*Description*:
+[.description]#Returns a string that is the Subject repeated a random number
of times between _min repeats_ and
+_max repeats_. If _max repeats_ is not supplied, it will return the Subject
repeated exactly _min repeats_ times.#
+
+[.description]#The _min repeats_ and _max repeats_ must be positive numbers,
with _max repeats_ greater than or equal
+to _min repeats_#
+
+[.description]#If either _min repeats_ or _max repeats_ is not a number, this
function call will result
+in an error.#
+
+
+*Subject Type*: [.subject]#String#
+
+*Arguments*:
+
+- [.argName]#_min repeats_# : [.argDesc]#The minimum number (inclusive) of
times to repeat the subject, or the exact number
+of times to repeat the subject if _max repeats_ is not supplied.#
+- [.argName]#_max repeats_# : [.argDesc]#The maximum number (inclusive) of
times to repeat the subject.#
+
+*Return Type*: [.returnType]#String#
+
+*Examples*:
+
+If we have an attribute named "str" with the value "abc",
+then the following Expressions will result in the following values:
+
+.Repeat Examples
+|================================================================
+| Expression | Value
+| `${str:repeat(1)}` | `abc`
+| `${str:repeat(2)}` | `abcabc`
+| `${str:repeat(1,2)}` | `abc` or `abcabc` (at random)
+| `${str:repeat( ${str:length()} )}` | `abc` or `abcabc` or `abcabcabc` (at
random)
+|================================================================
+
+
+
[[encode]]
== Encode/Decode Functions