You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mt...@apache.org on 2019/11/18 23:45:09 UTC

[nifi] branch master updated: NIFI-6782: Added repeat() String EL function

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 <ma...@apache.org>
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 <mt...@apache.org>
---
 .../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