Repository: nifi-minifi-cpp
Updated Branches:
  refs/heads/master 1ec799039 -> 45acefaac


MINIFICPP-369 Added multiple attribute/aggregate EL functions

This closes #325.

Signed-off-by: Aldrin Piri <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/commit/45acefaa
Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/45acefaa
Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/45acefaa

Branch: refs/heads/master
Commit: 45acefaaca12cd0d1325918f6f3400bf2163a452
Parents: 1ec7990
Author: Andrew I. Christianson <[email protected]>
Authored: Wed May 9 16:09:59 2018 -0400
Committer: Aldrin Piri <[email protected]>
Committed: Fri May 18 14:17:25 2018 -0400

----------------------------------------------------------------------
 EXPRESSIONS.md                                  | 253 +++++++++-
 extensions/expression-language/Expression.cpp   | 487 ++++++++++++++++++-
 .../impl/expression/Expression.h                |  49 +-
 .../ExpressionLanguageTests.cpp                 | 199 ++++++++
 4 files changed, 948 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/45acefaa/EXPRESSIONS.md
----------------------------------------------------------------------
diff --git a/EXPRESSIONS.md b/EXPRESSIONS.md
index d5a34bb..1056900 100644
--- a/EXPRESSIONS.md
+++ b/EXPRESSIONS.md
@@ -225,6 +225,17 @@ token, filename.
 - [`UUID`](#uuid)
 - [`literal`](#literal)
 
+### Evaluating Multiple Attributes
+
+- [`anyAttribute`](#anyattribute)
+- [`allAttributes`](#allattributes)
+- [`anyMatchingAttribute`](#anymatchingattribute)
+- [`allMatchingAttributes`](#allmatchingattributes)
+- [`anyDelineatedValue`](#anydelineatedvalue)
+- [`allDelineatedValues`](#alldelineatedvalues)
+- [`join`](#join)
+- [`count`](#count)
+
 ## Planned Features
 
 ### Searching
@@ -249,17 +260,6 @@ token, filename.
 - `nextInt`
 - `getStateValue`
 
-### Evaluating Multiple Attributes
-
-- `anyAttribute`
-- `allAttributes`
-- `anyMatchingAttribute`
-- `allMatchingAttributes`
-- `anyDelineatedValue`
-- `allDelineatedValues`
-- `join`
-- `count`
-
 ## Unsupported Features
 
 The following EL features are currently not supported, and no support is
@@ -1589,3 +1589,234 @@ to evaluate additional functions against.
 **Examples**: `${literal(2):gt(1)}` returns true.  `${literal(
 ${allMatchingAttributes('a.*'):count()} ):gt(3)}` returns true if there are
 more than 3 attributes whose names begin with the letter a.
+
+## Evaluating Multiple Attributes
+
+When it becomes necessary to evaluate the same conditions against multiple
+attributes, this can be accomplished by means of the and and or functions.
+However, this quickly becomes tedious, error-prone, and difficult to maintain.
+For this reason, NiFi Expression Language provides several functions for
+evaluating the same conditions against groups of attributes at the same time.
+
+### anyAttribute
+
+**Description**: Checks to see if any of the given attributes, match the given
+condition. This function has no subject and takes one or more arguments that
+are the names of attributes to which the remainder of the Expression is to be
+applied. If any of the attributes specified, when evaluated against the rest of
+the Expression, returns a value of `true`, then this function will return
+`true`.  Otherwise, this function will return `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Attribute Names | One or more attribute names to evaluate |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "filename" contains "file.txt" consider
+the following examples:
+
+| Expression | Value |
+| - | - |
+| `${anyAttribute("abc", "xyz"):contains("bye")}` | `true` |
+| `${anyAttribute("filename","xyz"):toUpper():contains("e")}` | `false` |
+
+### allAttributes
+
+**Description**: Checks to see if all of the given attributes match the given
+condition. This function has no subject and takes one or more arguments that
+are the names of attributes to which the remainder of the Expression is to be
+applied. If all of the attributes specified, when evaluated against the rest of
+the Expression, returns a value of `true`, then this function will return
+`true`. Otherwise, this function will return `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Attribute Names | One or more attribute names to evaluate |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "filename" contains "file.txt" consider
+the following examples:
+
+| Expression | Value |
+| - | - |
+| `${allAttributes("abc", "xyz"):contains("world")}` | `true` |
+| `${allAttributes("abc", "filename","xyz"):toUpper():contains("e")}` | 
`false` |
+
+### anyMatchingAttribute
+
+**Description**: Checks to see if any of the given attributes, match the given
+condition. This function has no subject and takes one or more arguments that
+are Regular Expressions to match against attribute names. Any attribute whose
+name matches one of the supplied Regular Expressions will be evaluated against
+the rest of the Expression. If any of the attributes specified, when evaluated
+against the rest of the Expression, returns a value of `true`, then this
+function will return `true`. Otherwise, this function will return `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Regex | One or more Regular Expressions to evaluate against attribute names |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "filename" contains "file.txt" consider
+the following examples:
+
+| Expression | Value |
+| - | - |
+| `${anyMatchingAttribute("[ax].*"):contains('bye')}` | `true` |
+| `${anyMatchingAttribute(".*"):isNull()}` | `false` |
+
+### allMatchingAttributes
+
+**Description**: Checks to see if any of the given attributes, match the given
+condition. This function has no subject and takes one or more arguments that
+are Regular Expressions to match against attribute names. Any attribute whose
+name matches one of the supplied Regular Expressions will be evaluated against
+the rest of the Expression. If all of the attributes specified, when evaluated
+against the rest of the Expression, return a value of `true`, then this
+function will return `true`. Otherwise, this function will return `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Regex | One or more Regular Expressions to evaluate against attribute names |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "filename" contains "file.txt" consider
+the following examples:
+
+| Expression | Value |
+| - | - |
+| `${allMatchingAttributes("[ax].*"):contains("world")}` | `true` |
+| `${allMatchingAttributes(".*"):isNull()}` | `false` |
+| `${allMatchingAttributes("f.*"):count()}` | `1` |
+
+### anyDelineatedValue
+
+**Description**: Splits a String apart according to a delimiter that is
+provided, and then evaluates each of the values against the rest of the
+Expression. If the Expression, when evaluated against any of the individual
+values, returns `true`, this function returns `true`. Otherwise, the function
+returns `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Delineated Value | The value that is delineated. This is generally an 
embedded Expression, though it does not have to be. |
+| Delimiter | The value to use to split apart the delineatedValue argument. |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "number_list" attribute contains the value
+"1,2,3,4,5", and the "word_list" attribute contains the value "the,and,or,not",
+consider the following examples:
+
+| Expression | Value |
+| - | - |
+| `${anyDelineatedValue("${number_list}", ","):contains("5")}` | `true` |
+| `${anyDelineatedValue("this that and", ","):equals("${word_list}")}` | 
`false` |
+
+### allDelineatedValues
+
+**Description**: Splits a String apart according to a delimiter that is
+provided, and then evaluates each of the values against the rest of the
+Expression. If the Expression, when evaluated against all of the individual
+values, returns `true` in each case, then this function returns `true`.
+Otherwise, the function returns `false`.
+
+**Subject Type**: No Subject
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Delineated Value | The value that is delineated. This is generally an 
embedded Expression, though it does not have to be. |
+| Delimiter | The value to use to split apart the delineatedValue argument. |
+
+**Return Type**: Boolean
+
+**Examples**: Given that the "number_list" attribute contains the value
+"1,2,3,4,5", and the "word_list" attribute contains the value
+"those,known,or,not", consider the following examples:
+
+| Expression | Value |
+| - | - |
+| `${allDelineatedValues("${word_list}", ","):contains("o")}` | `true` |
+| `${allDelineatedValues("${number_list}", ","):count()}` | `4` |
+| `${allDelineatedValues("${number_list}", ","):matches("[0-9]+")}` | `true` |
+| `${allDelineatedValues("${word_list}", ","):matches('e')}` | `false` |
+
+### join
+
+**Description**: Aggregate function that concatenates multiple values with the
+specified delimiter. This function may be used only in conjunction with the
+`allAttributes`, `allMatchingAttributes`, and `allDelineatedValues` functions.
+
+**Subject Type**: String
+
+**Arguments**: 
+
+| Argument | Description |
+| - | - |
+| Delimiter | The String delimiter to use when joining values |
+
+**Return Type**: String
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "filename" contains "file.txt" consider
+the following examples:
+
+| Expression | Value |
+| - | - |
+| `${allMatchingAttributes("[ax].*"):substringBefore(" "):join("-")}` | 
`hello-good` |
+| `${allAttributes("abc", "xyz"):join(" now")}` | `hello world nowgood bye 
world now` |
+
+### count
+
+**Description**: Aggregate function that counts the number of non-null,
+non-false values returned by the `allAttributes`, `allMatchingAttributes`, and
+`allDelineatedValues`. This function may be used only in conjunction with the
+`allAttributes`, `allMatchingAttributes`, and `allDelineatedValues` functions.
+
+**Subject Type**: Any
+
+**Arguments**: No arguments
+
+**Return Type**: Number
+
+**Examples**: Given that the "abc" attribute contains the value "hello world",
+"xyz" contains "good bye world", and "number_list" contains "1,2,3,4,5"
+consider the following examples:
+
+| Expression | Value |
+| - | - |
+| `${allMatchingAttributes("[ax].*"):substringBefore(" "):count()}` | `2` |
+| `${allAttributes("abc", "xyz"):contains("world"):count()}` | `1` |
+| `${allDelineatedValues(${number_list}, ","):count()}` | `5` |
+| `${allAttributes("abc", "non-existent-attr", "xyz"):count()}` | `2` |
+| `${allMatchingAttributes(".*"):length():gt(10):count()}` | `2` |

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/45acefaa/extensions/expression-language/Expression.cpp
----------------------------------------------------------------------
diff --git a/extensions/expression-language/Expression.cpp 
b/extensions/expression-language/Expression.cpp
index 4a93b1c..6c5b5b0 100644
--- a/extensions/expression-language/Expression.cpp
+++ b/extensions/expression-language/Expression.cpp
@@ -52,12 +52,13 @@ Expression make_static(std::string val) {
   return Expression(Value(val));
 }
 
-Expression make_dynamic(const std::function<Value(const Parameters &params)> 
&val_fn) {
+Expression make_dynamic(const std::function<Value(const Parameters &params,
+                                                  const 
std::vector<Expression> &sub_exprs)> &val_fn) {
   return Expression(Value(), val_fn);
 }
 
 Expression make_dynamic_attr(const std::string &attribute_id) {
-  return make_dynamic([attribute_id](const Parameters &params) -> Value {
+  return make_dynamic([attribute_id](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
     std::string result;
     if (params.flow_file.lock()->getAttribute(attribute_id, result)) {
       return Value(result);
@@ -1578,17 +1579,27 @@ Expression make_dynamic_function_incomplete(const 
std::string &function_name,
     throw std::runtime_error(message_ss.str());
   }
 
-  auto result = make_dynamic([=](const Parameters &params) -> Value {
-    std::vector<Value> evaluated_args;
+  if (!args.empty() && args[0].is_multi()) {
+    std::vector<Expression> multi_args;
 
-    for (const auto &arg : args) {
-      evaluated_args.emplace_back(arg(params));
+    for (auto it = std::next(args.begin()); it != args.end(); ++it) {
+      multi_args.emplace_back(*it);
     }
 
-    return T(evaluated_args);
-  });
+    return args[0].compose_multi([=](const std::vector<Value> &args) -> Value {
+      return T(args);
+    }, multi_args);
+  } else {
+    return make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+      std::vector<Value> evaluated_args;
 
-  return result;
+      for (const auto &arg : args) {
+        evaluated_args.emplace_back(arg(params));
+      }
+
+      return T(evaluated_args);
+    });
+  }
 }
 
 Value expr_literal(const std::vector<Value> &args) {
@@ -1690,6 +1701,366 @@ Value expr_ifElse(const std::vector<Value> &args) {
   }
 }
 
+Expression make_allAttributes(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() < 1) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 1
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool all_true = true;
+
+    for (const auto &sub_expr : sub_exprs) {
+      all_true = all_true && sub_expr(params).asBoolean();
+    }
+
+    return Value(all_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &arg : args) {
+      out_exprs.emplace_back(make_dynamic([=](const Parameters &params,
+                                              const std::vector<Expression> 
&sub_exprs) -> Value {
+        std::string attr_id;
+        attr_id = arg(params).asString();
+        std::string attr_val;
+
+        if (params.flow_file.lock()->getAttribute(attr_id, attr_val)) {
+          return Value(attr_val);
+        } else {
+          return Value();
+        }
+      }));
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+Expression make_anyAttribute(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() < 1) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 1
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool any_true = false;
+
+    for (const auto &sub_expr : sub_exprs) {
+      any_true = any_true || sub_expr(params).asBoolean();
+    }
+
+    return Value(any_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &arg : args) {
+      out_exprs.emplace_back(make_dynamic([=](const Parameters &params,
+                                              const std::vector<Expression> 
&sub_exprs) -> Value {
+        std::string attr_id;
+        attr_id = arg(params).asString();
+        std::string attr_val;
+
+        if (params.flow_file.lock()->getAttribute(attr_id, attr_val)) {
+          return Value(attr_val);
+        } else {
+          return Value();
+        }
+      }));
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+#ifdef EXPRESSION_LANGUAGE_USE_REGEX
+
+Expression make_allMatchingAttributes(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() < 1) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 1
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool all_true = !sub_exprs.empty();
+
+    for (const auto &sub_expr : sub_exprs) {
+      all_true = all_true && sub_expr(params).asBoolean();
+    }
+
+    return Value(all_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &arg : args) {
+      const std::regex attr_regex = std::regex(arg(params).asString());
+      auto attrs = params.flow_file.lock()->getAttributes();
+
+      for (const auto &attr : attrs) {
+        if (std::regex_match(attr.first.begin(), attr.first.end(), 
attr_regex)) {
+          out_exprs.emplace_back(make_dynamic([=](const Parameters &params,
+                                                  const 
std::vector<Expression> &sub_exprs) -> Value {
+            std::string attr_val;
+
+            if (params.flow_file.lock()->getAttribute(attr.first, attr_val)) {
+              return Value(attr_val);
+            } else {
+              return Value();
+            }
+          }));
+        }
+      }
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+Expression make_anyMatchingAttribute(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() < 1) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 1
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool any_true = false;
+
+    for (const auto &sub_expr : sub_exprs) {
+      any_true = any_true || sub_expr(params).asBoolean();
+    }
+
+    return Value(any_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &arg : args) {
+      const std::regex attr_regex = std::regex(arg(params).asString());
+      auto attrs = params.flow_file.lock()->getAttributes();
+
+      for (const auto &attr : attrs) {
+        if (std::regex_match(attr.first.begin(), attr.first.end(), 
attr_regex)) {
+          out_exprs.emplace_back(make_dynamic([=](const Parameters &params,
+                                                  const 
std::vector<Expression> &sub_exprs) -> Value {
+            std::string attr_val;
+
+            if (params.flow_file.lock()->getAttribute(attr.first, attr_val)) {
+              return Value(attr_val);
+            } else {
+              return Value();
+            }
+          }));
+        }
+      }
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+#endif  // EXPRESSION_LANGUAGE_USE_REGEX
+
+Expression make_allDelineatedValues(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() != 2) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 2
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool all_true = !sub_exprs.empty();
+
+    for (const auto &sub_expr : sub_exprs) {
+      all_true = all_true && sub_expr(params).asBoolean();
+    }
+
+    return Value(all_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &val : 
utils::StringUtils::split(args[0](params).asString(), 
args[1](params).asString())) {
+      out_exprs.emplace_back(make_static(val));
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+Expression make_anyDelineatedValue(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() != 2) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 2
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto result = make_dynamic([=](const Parameters &params, const 
std::vector<Expression> &sub_exprs) -> Value {
+    std::vector<Value> evaluated_args;
+
+    bool any_true = false;
+
+    for (const auto &sub_expr : sub_exprs) {
+      any_true = any_true || sub_expr(params).asBoolean();
+    }
+
+    return Value(any_true);
+  });
+
+  result.make_multi([=](const Parameters &params) -> std::vector<Expression> {
+    std::vector<Expression> out_exprs;
+
+    for (const auto &val : 
utils::StringUtils::split(args[0](params).asString(), 
args[1](params).asString())) {
+      out_exprs.emplace_back(make_static(val));
+    }
+
+    return out_exprs;
+  });
+
+  return result;
+}
+
+Expression make_count(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() != 1) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 1
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  if (!args[0].is_multi()) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called against singular expression.";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  return args[0].make_aggregate([](const Parameters &params,
+                                   const std::vector<Expression> &sub_exprs) 
-> Value {
+    uint64_t count = 0;
+    for (const auto &sub_expr : sub_exprs) {
+      if (sub_expr(params).asBoolean()) {
+        count++;
+      }
+    }
+    return Value(count);
+  });
+}
+
+Expression make_join(const std::string &function_name, const 
std::vector<Expression> &args) {
+  if (args.size() != 2) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called with "
+               << args.size()
+               << " argument(s), but "
+               << 2
+               << " are required";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  if (!args[0].is_multi()) {
+    std::stringstream message_ss;
+    message_ss << "Expression language function "
+               << function_name
+               << " called against singular expression.";
+    throw std::runtime_error(message_ss.str());
+  }
+
+  auto delim_expr = args[1];
+
+  return args[0].make_aggregate([delim_expr](const Parameters &params,
+                                             const std::vector<Expression> 
&sub_exprs) -> Value {
+    std::string delim = delim_expr(params).asString();
+    std::stringstream out_ss;
+    bool first = true;
+
+    for (const auto &sub_expr : sub_exprs) {
+      if (!first) {
+        out_ss << delim;
+      }
+      out_ss << sub_expr(params).asString();
+      first = false;
+    }
+
+    return Value(out_ss.str());
+  });
+}
+
 Expression make_dynamic_function(const std::string &function_name,
                                  const std::vector<Expression> &args) {
   if (function_name == "hostname") {
@@ -1769,6 +2140,10 @@ Expression make_dynamic_function(const std::string 
&function_name,
     return make_dynamic_function_incomplete<expr_matches>(function_name, args, 
1);
   } else if (function_name == "find") {
     return make_dynamic_function_incomplete<expr_find>(function_name, args, 1);
+  } else if (function_name == "allMatchingAttributes") {
+    return make_allMatchingAttributes(function_name, args);
+  } else if (function_name == "anyMatchingAttribute") {
+    return make_anyMatchingAttribute(function_name, args);
 #endif  // EXPRESSION_LANGUAGE_USE_REGEX
   } else if (function_name == "trim") {
     return make_dynamic_function_incomplete<expr_trim>(function_name, args, 0);
@@ -1822,6 +2197,18 @@ Expression make_dynamic_function(const std::string 
&function_name,
     return make_dynamic_function_incomplete<expr_not>(function_name, args, 0);
   } else if (function_name == "ifElse") {
     return make_dynamic_function_incomplete<expr_ifElse>(function_name, args, 
2);
+  } else if (function_name == "allAttributes") {
+    return make_allAttributes(function_name, args);
+  } else if (function_name == "anyAttribute") {
+    return make_anyAttribute(function_name, args);
+  } else if (function_name == "allDelineatedValues") {
+    return make_allDelineatedValues(function_name, args);
+  } else if (function_name == "anyDelineatedValue") {
+    return make_anyDelineatedValue(function_name, args);
+  } else if (function_name == "count") {
+    return make_count(function_name, args);
+  } else if (function_name == "join") {
+    return make_join(function_name, args);
   } else {
     std::string msg("Unknown expression function: ");
     msg.append(function_name);
@@ -1843,7 +2230,7 @@ Expression make_function_composition(const Expression 
&arg,
   return expr;
 }
 
-bool Expression::isDynamic() const {
+bool Expression::is_dynamic() const {
   if (val_fn_) {
     return true;
   } else {
@@ -1852,28 +2239,42 @@ bool Expression::isDynamic() const {
 }
 
 Expression Expression::operator+(const Expression &other_expr) const {
-  if (isDynamic() && other_expr.isDynamic()) {
+  if (is_dynamic() && other_expr.is_dynamic()) {
     auto val_fn = val_fn_;
     auto other_val_fn = other_expr.val_fn_;
-    return make_dynamic([val_fn, other_val_fn](const Parameters &params) -> 
Value {
-      Value result = val_fn(params);
-      return Value(result.asString().append(other_val_fn(params).asString()));
+    auto sub_expr_generator = sub_expr_generator_;
+    auto other_sub_expr_generator = other_expr.sub_expr_generator_;
+    return make_dynamic([val_fn,
+                            other_val_fn,
+                            sub_expr_generator,
+                            other_sub_expr_generator](const Parameters &params,
+                                                      const 
std::vector<Expression> &sub_exprs) -> Value {
+      Value result = val_fn(params, sub_expr_generator(params));
+      return Value(result.asString().append(other_val_fn(params, 
other_sub_expr_generator(params)).asString()));
     });
-  } else if (isDynamic() && !other_expr.isDynamic()) {
+  } else if (is_dynamic() && !other_expr.is_dynamic()) {
     auto val_fn = val_fn_;
     auto other_val = other_expr.val_;
-    return make_dynamic([val_fn, other_val](const Parameters &params) -> Value 
{
-      Value result = val_fn(params);
+    auto sub_expr_generator = sub_expr_generator_;
+    return make_dynamic([val_fn,
+                            other_val,
+                            sub_expr_generator](const Parameters &params,
+                                                const std::vector<Expression> 
&sub_exprs) -> Value {
+      Value result = val_fn(params, sub_expr_generator(params));
       return Value(result.asString().append(other_val.asString()));
     });
-  } else if (!isDynamic() && other_expr.isDynamic()) {
+  } else if (!is_dynamic() && other_expr.is_dynamic()) {
     auto val = val_;
     auto other_val_fn = other_expr.val_fn_;
-    return make_dynamic([val, other_val_fn](const Parameters &params) -> Value 
{
+    auto other_sub_expr_generator = other_expr.sub_expr_generator_;
+    return make_dynamic([val,
+                            other_val_fn,
+                            other_sub_expr_generator](const Parameters &params,
+                                                      const 
std::vector<Expression> &sub_exprs) -> Value {
       Value result(val);
-      return Value(result.asString().append(other_val_fn(params).asString()));
+      return Value(result.asString().append(other_val_fn(params, 
other_sub_expr_generator(params)).asString()));
     });
-  } else if (!isDynamic() && !other_expr.isDynamic()) {
+  } else if (!is_dynamic() && !other_expr.is_dynamic()) {
     std::string result(val_.asString());
     result.append(other_expr.val_.asString());
     return make_static(result);
@@ -1883,13 +2284,53 @@ Expression Expression::operator+(const Expression 
&other_expr) const {
 }
 
 Value Expression::operator()(const Parameters &params) const {
-  if (isDynamic()) {
-    return val_fn_(params);
+  if (is_dynamic()) {
+    return val_fn_(params, sub_expr_generator_(params));
   } else {
     return val_;
   }
 }
 
+Expression Expression::compose_multi(const std::function<Value(const 
std::vector<Value> &)> fn,
+                                     const std::vector<Expression> &args) 
const {
+  auto result = make_dynamic(val_fn_);
+  auto compose_expr_generator = sub_expr_generator_;
+
+  result.sub_expr_generator_ = [=](const Parameters &params) -> 
std::vector<Expression> {
+    auto sub_exprs = compose_expr_generator(params);
+    std::vector<Expression> out_exprs{};
+    for (const auto &sub_expr : sub_exprs) {
+      out_exprs.emplace_back(make_dynamic([=](const Parameters &params,
+                                              const std::vector<Expression> 
&sub_exprs) {
+        std::vector<Value> evaluated_args;
+
+        evaluated_args.emplace_back(sub_expr(params));
+
+        for (const auto &arg : args) {
+          evaluated_args.emplace_back(arg(params));
+        }
+
+        return fn(evaluated_args);
+      }));
+    }
+    return out_exprs;
+  };
+
+  result.is_multi_ = true;
+
+  return result;
+}
+
+Expression Expression::make_aggregate(std::function<Value(const Parameters 
&params,
+                                                          const 
std::vector<Expression> &sub_exprs)> val_fn) const {
+  auto sub_expr_generator = sub_expr_generator_;
+  return make_dynamic([sub_expr_generator,
+                          val_fn](const Parameters &params,
+                                  const std::vector<Expression> &sub_exprs) -> 
Value {
+    return val_fn(params, sub_expr_generator(params));
+  });
+}
+
 } /* namespace expression */
 } /* namespace minifi */
 } /* namespace nifi */

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/45acefaa/extensions/expression-language/impl/expression/Expression.h
----------------------------------------------------------------------
diff --git a/extensions/expression-language/impl/expression/Expression.h 
b/extensions/expression-language/impl/expression/Expression.h
index f123f77..b7bf676 100644
--- a/extensions/expression-language/impl/expression/Expression.h
+++ b/extensions/expression-language/impl/expression/Expression.h
@@ -42,7 +42,9 @@ typedef struct {
   std::weak_ptr<core::FlowFile> flow_file;
 } Parameters;
 
-static const std::function<Value(const Parameters &params)> NOOP_FN;
+class Expression;
+
+static const std::function<Value(const Parameters &params, const 
std::vector<Expression> &sub_exprs)> NOOP_FN;
 
 /**
  * A NiFi expression language expression.
@@ -55,10 +57,12 @@ class Expression {
   }
 
   explicit Expression(Value val,
-                      std::function<Value(const Parameters &)> val_fn = 
NOOP_FN)
+                      std::function<Value(const Parameters &, const 
std::vector<Expression> &)> val_fn = NOOP_FN)
       : val_fn_(std::move(val_fn)),
-        fn_args_() {
+        fn_args_(),
+        is_multi_(false) {
     val_ = val;
+    sub_expr_generator_ = [](const Parameters &params) -> 
std::vector<Expression> { return {}; };
   }
 
   /**
@@ -68,7 +72,14 @@ class Expression {
    *
    * @return true if expression is dynamic
    */
-  bool isDynamic() const;
+  bool is_dynamic() const;
+
+  /**
+   * Whether or not this expression is a multi-expression.
+   */
+  bool is_multi() const {
+    return is_multi_;
+  }
 
   /**
    * Combine this expression with another expression by concatenation. 
Intermediate results
@@ -87,10 +98,35 @@ class Expression {
    */
   Value operator()(const Parameters &params) const;
 
+  /**
+   * Turn this expression into a multi-expression which generates 
subexpressions dynamically.
+   *
+   * @param sub_expr_generator function which generates expressions according 
to given params
+   */
+  void make_multi(std::function<std::vector<Expression>(const Parameters 
&params)> sub_expr_generator) {
+    this->sub_expr_generator_ = std::move(sub_expr_generator);
+    is_multi_ = true;
+  }
+
+  /**
+   * Composes a multi-expression statically with the given fn.
+   *
+   * @param fn Value function to compose with
+   * @param args function arguments
+   * @return composed multi-expression
+   */
+  Expression compose_multi(const std::function<Value(const std::vector<Value> 
&)> fn,
+                           const std::vector<Expression> &args) const;
+
+  Expression make_aggregate(std::function<Value(const Parameters &params,
+                                                const std::vector<Expression> 
&sub_exprs)> val_fn) const;
+
  protected:
   Value val_;
-  std::function<Value(const Parameters &params)> val_fn_;
+  std::function<Value(const Parameters &params, const std::vector<Expression> 
&sub_exprs)> val_fn_;
   std::vector<Expression> fn_args_;
+  std::function<std::vector<Expression>(const Parameters &params)> 
sub_expr_generator_;
+  bool is_multi_;
 };
 
 /**
@@ -115,7 +151,8 @@ Expression make_static(std::string val);
  * @param val_fn
  * @return
  */
-Expression make_dynamic(const std::function<Value(const Parameters &params)> 
&val_fn);
+Expression make_dynamic(const std::function<Value(const Parameters &params,
+                                                  const 
std::vector<Expression> &sub_exprs)> &val_fn);
 
 /**
  * Creates a dynamic expression which evaluates the given flow file attribute.

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/45acefaa/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
----------------------------------------------------------------------
diff --git 
a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp 
b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
index 7d98644..232deb4 100644
--- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
+++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
@@ -1285,3 +1285,202 @@ TEST_CASE("Encode Decode B64", 
"[expressionEncodeDecodeB64]") {  // NOLINT
   flow_file_a->addAttribute("message", "Zero > One < \"two!\" & 'true'");
   REQUIRE("Zero > One < \"two!\" & 'true'" == expr({flow_file_a}).asString());
 }
+
+TEST_CASE("All Contains", "[expressionAllContains]") {  // NOLINT
+  auto expr = expression::compile("${allAttributes('a', 
'b'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello 1");
+  flow_file_a->addAttribute("b", "hello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Contains 2", "[expressionAllContains2]") {  // NOLINT
+  auto expr = expression::compile("${allAttributes('a', 
'b'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Contains", "[expressionAnyContains]") {  // NOLINT
+  auto expr = expression::compile("${anyAttribute('a', 
'b'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Contains 2", "[expressionAnyContains2]") {  // NOLINT
+  auto expr = expression::compile("${anyAttribute('a', 
'b'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "mello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+#ifdef EXPRESSION_LANGUAGE_USE_REGEX
+
+TEST_CASE("All Matching Contains", "[expressionAllMatchingContains]") {  // 
NOLINT
+  auto expr = 
expression::compile("${allMatchingAttributes('xyz_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "hello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Matching Contains 2", "[expressionAllMatchingContains2]") {  // 
NOLINT
+  auto expr = 
expression::compile("${allMatchingAttributes('abc_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "hello 2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Matching Contains 3", "[expressionAllMatchingContains3]") {  // 
NOLINT
+  auto expr = 
expression::compile("${allMatchingAttributes('abc_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("abc_2", "hello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Matching Contains 4", "[expressionAllMatchingContains4]") {  // 
NOLINT
+  auto expr = 
expression::compile("${allMatchingAttributes('xyz_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Matching Contains", "[expressionAnyMatchingContains]") {  // 
NOLINT
+  auto expr = 
expression::compile("${anyMatchingAttribute('xyz_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "mello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Matching Contains 2", "[expressionAnyMatchingContains2]") {  // 
NOLINT
+  auto expr = 
expression::compile("${anyMatchingAttribute('abc_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "mello 2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Matching Contains 3", "[expressionAnyMatchingContains3]") {  // 
NOLINT
+  auto expr = 
expression::compile("${anyMatchingAttribute('abc_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("abc_1", "hello 1");
+  flow_file_a->addAttribute("xyz_2", "mello 2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Matching Contains 4", "[expressionAnyMatchingContains4]") {  // 
NOLINT
+  auto expr = 
expression::compile("${anyMatchingAttribute('abc_.*'):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("xyz_1", "mello 1");
+  flow_file_a->addAttribute("xyz_2", "mello 2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+#endif  // EXPRESSION_LANGUAGE_USE_REGEX
+
+TEST_CASE("All Delineated Contains", "[expressionAllDelineatedContains]") {  
// NOLINT
+  auto expr = expression::compile("${allDelineatedValues(${word_list}, 
\",\"):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("word_list", "hello_1,hello_2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Delineated Contains 2", "[expressionAllDelineatedContains2]") { 
 // NOLINT
+  auto expr = expression::compile("${allDelineatedValues(${word_list}, 
\",\"):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("word_list", "hello_1,mello_2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("All Delineated Contains 3", "[expressionAllDelineatedContains3]") { 
 // NOLINT
+  auto expr = expression::compile("${allDelineatedValues(${word_list}, \" 
\"):contains('1,h')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("word_list", "hello_1,hello_2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Delineated Contains", "[expressionAnyDelineatedContains]") {  
// NOLINT
+  auto expr = expression::compile("${anyDelineatedValue(${word_list}, 
\",\"):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("word_list", "hello_1,mello_2");
+  REQUIRE(expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Any Delineated Contains 2", "[expressionAnyDelineatedContains2]") { 
 // NOLINT
+  auto expr = expression::compile("${anyDelineatedValue(${word_list}, 
\",\"):contains('hello')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("word_list", "mello_1,mello_2");
+  REQUIRE(!expr({flow_file_a}).asBoolean());
+}
+
+TEST_CASE("Count", "[expressionCount]") {  // NOLINT
+  auto expr = expression::compile("${allAttributes('a', 
'b'):contains('hello'):count()}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  REQUIRE(1 == expr({flow_file_a}).asUnsignedLong());
+}
+
+TEST_CASE("Count 2", "[expressionCount2]") {  // NOLINT
+  auto expr = expression::compile("${allAttributes('a', 
'b'):contains('mello'):count()}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "mello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  flow_file_a->addAttribute("c", "hello 3");
+  REQUIRE(2 == expr({flow_file_a}).asUnsignedLong());
+}
+
+TEST_CASE("Count 3", "[expressionCount3]") {  // NOLINT
+  auto expr = expression::compile("abc${allAttributes('a', 
'b'):contains('mello'):count()}xyz");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "mello 1");
+  flow_file_a->addAttribute("b", "mello 2");
+  flow_file_a->addAttribute("c", "hello 3");
+  REQUIRE("abc2xyz" == expr({flow_file_a}).asString());
+}
+
+TEST_CASE("Join", "[expressionJoin]") {  // NOLINT
+  auto expr = expression::compile("abc_${allAttributes('a', 
'b'):prepend('def_'):append('_ghi'):join(\"|\")}_xyz");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello");
+  flow_file_a->addAttribute("b", "mello");
+  REQUIRE("abc_def_hello_ghi|def_mello_ghi_xyz" == 
expr({flow_file_a}).asString());
+}
+
+TEST_CASE("Join 2", "[expressionJoin2]") {  // NOLINT
+  auto expr = expression::compile("abc_${allAttributes('a', 
'b'):join(\"|\"):prepend('def_'):append('_ghi')}_xyz");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  flow_file_a->addAttribute("a", "hello");
+  flow_file_a->addAttribute("b", "mello");
+  REQUIRE("abc_def_hello|mello_ghi_xyz" == expr({flow_file_a}).asString());
+}

Reply via email to