Repository: nifi-minifi-cpp
Updated Branches:
  refs/heads/master ebdfc21f8 -> 4c8cb0e0c


MINIFICPP-368 Implement subjectless EL functions

This closes #316.

Signed-off-by: Marc Parisi <[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/4c8cb0e0
Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/4c8cb0e0
Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/4c8cb0e0

Branch: refs/heads/master
Commit: 4c8cb0e0cde485f0c4b814d8ea7c5ff64d890fca
Parents: ebdfc21
Author: Andrew I. Christianson <[email protected]>
Authored: Wed May 2 12:58:22 2018 -0400
Committer: Marc Parisi <[email protected]>
Committed: Thu May 3 17:18:21 2018 -0400

----------------------------------------------------------------------
 EXPRESSIONS.md                                  | 90 +++++++++++++++++++-
 extensions/expression-language/Expression.cpp   | 79 ++++++++++++++++-
 .../ExpressionLanguageTests.cpp                 | 21 +++++
 3 files changed, 184 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/4c8cb0e0/EXPRESSIONS.md
----------------------------------------------------------------------
diff --git a/EXPRESSIONS.md b/EXPRESSIONS.md
index 5ce4c39..f2a4876 100644
--- a/EXPRESSIONS.md
+++ b/EXPRESSIONS.md
@@ -211,6 +211,13 @@ token, filename.
 - [`urlEncode`](#urlencode)
 - [`urlDecode`](#urldecode)
 
+### Subjectless Functions
+
+- [`ip`](#ip)
+- [`hostname`](#hostname)
+- [`UUID`](#uuid)
+- [`literal`](#literal)
+
 ## Planned Features
 
 ### String Manipulation
@@ -242,11 +249,7 @@ token, filename.
 
 ### Subjectless Functions
 
-- `ip`
-- `hostname`
-- `UUID`
 - `nextInt`
-- `literal`
 - `getStateValue`
 
 ### Evaluating Multiple Attributes
@@ -1377,3 +1380,82 @@ human-readable form.
 If we have a URL-Encoded attribute named "url" with the value
 "some%20value%20with%20spaces", then the Expression `${url:urlDecode()}` will
 return "some value with spaces".
+
+## Subjectless Functions
+
+While the majority of functions in the Expression Language are called by using
+the syntax `${attributeName:function()}`, there exist a few functions that are
+not expected to have subjects. In this case, the attribute name is not present.
+For example, the IP address of the machine can be obtained by using the
+Expression `${ip()}`. All of the functions in this section are to be called
+without a subject. Attempting to call a subjectless function and provide it a
+subject will result in an error when validating the function.
+
+### ip
+
+**Description**: Returns the IP address of the machine.
+
+**Subject Type**: No subject
+
+**Arguments**: No arguments
+
+**Return Type**: String
+
+**Examples**: The IP address of the machine can be obtained by using the 
Expression `${ip()}`.
+
+### hostname
+
+**Description**: eturns the Hostname of the machine. An optional argument of
+type Boolean can be provided to specify whether or not the Fully Qualified
+Domain Name should be used. If `false`, or not specified, the hostname will not
+be fully qualified. If the argument is true but the fully qualified hostname
+cannot be resolved, the simple hostname will be returned.
+
+**Subject Type**: No subject
+
+**Arguments**:
+
+| Argument | Description |
+| - | - |
+| Fully Qualified | Optional parameter that specifies whether or not the 
hostname should be fully qualified. If not specified, defaults to `false`. |
+
+**Return Type**: String
+
+**Examples**: The fully qualified hostname of the machine can be obtained by
+using the Expression `${hostname(true)}`, while the simple hostname can be
+obtained by using either `${hostname(false)}` or simply `${hostname()}`.
+
+### UUID
+
+**Description**: Returns a randomly generated UUID.
+
+**Subject Type**: No subject
+
+**Arguments**: No arguments
+
+**Return Type**: String
+
+**Examples**: `${UUID()}` returns a value similar to
+"de305d54-75b4-431b-adb2-eb6b9e546013"
+
+### literal
+
+**Description**: Returns its argument as a literal String value. This is useful
+in order to treat a string or a number at the beginning of an Expression as an
+actual value, rather than treating it as an attribute name. Additionally, it
+can be used when the argument is an embedded Expression that we would then like
+to evaluate additional functions against.
+
+**Subject Type**: No subject
+
+**Arguments**:
+
+| Argument | Description |
+| - | - |
+| value | The value to be treated as a literal string, number, or boolean 
value. |
+
+**Return Type**: String
+
+**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.

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/4c8cb0e0/extensions/expression-language/Expression.cpp
----------------------------------------------------------------------
diff --git a/extensions/expression-language/Expression.cpp 
b/extensions/expression-language/Expression.cpp
index 44a3981..9722028 100644
--- a/extensions/expression-language/Expression.cpp
+++ b/extensions/expression-language/Expression.cpp
@@ -29,6 +29,8 @@
 #include <expression/Expression.h>
 #include <regex>
 #include <curl/curl.h>
+#include <netdb.h>
+#include <arpa/inet.h>
 #include "Driver.h"
 
 namespace org {
@@ -68,9 +70,78 @@ Value expr_hostname(const std::vector<Value> &args) {
   char hostname[1024];
   hostname[1023] = '\0';
   gethostname(hostname, 1023);
+
+  if (args.size() > 0 && args[0].asBoolean()) {
+    int status;
+    struct addrinfo hints, *result, *addr_cursor;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_CANONNAME;
+
+    status = getaddrinfo(hostname, nullptr, &hints, &result);
+
+    if (status) {
+      std::string message("Failed to resolve local hostname to discover IP: ");
+      message.append(gai_strerror(status));
+      throw std::runtime_error(message);
+    }
+
+    for (addr_cursor = result; addr_cursor != nullptr; addr_cursor = 
addr_cursor->ai_next) {
+      if (strlen(addr_cursor->ai_canonname) > 0) {
+        std::string c_host(addr_cursor->ai_canonname);
+        freeaddrinfo(result);
+        return Value(c_host);
+      }
+    }
+
+    freeaddrinfo(result);
+  }
+
   return Value(std::string(hostname));
 }
 
+Value expr_ip(const std::vector<Value> &args) {
+  char hostname[1024];
+  hostname[1023] = '\0';
+  gethostname(hostname, 1023);
+
+  int status;
+  char ip_str[INET6_ADDRSTRLEN];
+  struct sockaddr_in *addr;
+  struct addrinfo hints, *result, *addr_cursor;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_INET;
+
+  status = getaddrinfo(hostname, nullptr, &hints, &result);
+
+  if (status) {
+    std::string message("Failed to resolve local hostname to discover IP: ");
+    message.append(gai_strerror(status));
+    throw std::runtime_error(message);
+  }
+
+  for (addr_cursor = result; addr_cursor != nullptr; addr_cursor = 
addr_cursor->ai_next) {
+    if (addr_cursor->ai_family == AF_INET) {
+      addr = reinterpret_cast<struct sockaddr_in *>(addr_cursor->ai_addr);
+      inet_ntop(addr_cursor->ai_family, &(addr->sin_addr), ip_str, 
sizeof(ip_str));
+      freeaddrinfo(result);
+      return Value(std::string(ip_str));
+    }
+  }
+
+  freeaddrinfo(result);
+  return Value();
+}
+
+Value expr_uuid(const std::vector<Value> &args) {
+  uuid_t uuid;
+  utils::IdGenerator::getIdGenerator()->generate(uuid);
+  char uuid_str[37];
+  uuid_unparse_lower(uuid, uuid_str);
+  return Value(std::string(uuid_str));
+}
+
 Value expr_toUpper(const std::vector<Value> &args) {
   std::string result = args[0].asString();
   std::transform(result.begin(), result.end(), result.begin(), ::toupper);
@@ -1112,7 +1183,7 @@ Value expr_unescapeCsv(const std::vector<Value> &args) {
 
 Value expr_urlEncode(const std::vector<Value> &args) {
   auto arg_0 = args[0].asString();
-  CURL *curl = curl_easy_init();
+  CURL * curl = curl_easy_init();
   if (curl != nullptr) {
     char *output = curl_easy_escape(curl,
                                     arg_0.c_str(),
@@ -1133,7 +1204,7 @@ Value expr_urlEncode(const std::vector<Value> &args) {
 
 Value expr_urlDecode(const std::vector<Value> &args) {
   auto arg_0 = args[0].asString();
-  CURL *curl = curl_easy_init();
+  CURL * curl = curl_easy_init();
   if (curl != nullptr) {
     int out_len;
     char *output = curl_easy_unescape(curl,
@@ -1453,6 +1524,10 @@ Expression make_dynamic_function(const std::string 
&function_name,
                                  const std::vector<Expression> &args) {
   if (function_name == "hostname") {
     return make_dynamic_function_incomplete<expr_hostname>(function_name, 
args, 0);
+  } else if (function_name == "ip") {
+    return make_dynamic_function_incomplete<expr_ip>(function_name, args, 0);
+  } else if (function_name == "UUID") {
+    return make_dynamic_function_incomplete<expr_uuid>(function_name, args, 0);
   } else if (function_name == "toUpper") {
     return make_dynamic_function_incomplete<expr_toUpper>(function_name, args, 
1);
   } else if (function_name == "toLower") {

http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/4c8cb0e0/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 40713c2..5b687d7 100644
--- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
+++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp
@@ -1168,3 +1168,24 @@ TEST_CASE("Encode Decode URL", 
"[expressionEncodeDecodeURL]") {  // NOLINT
   flow_file_a->addAttribute("message", "some value with spaces");
   REQUIRE("some value with spaces" == expr({flow_file_a}).asString());
 }
+
+TEST_CASE("IP", "[expressionIP]") {  // NOLINT
+  auto expr = expression::compile("${ip()}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  REQUIRE("" != expr({flow_file_a}).asString());
+}
+
+TEST_CASE("Full Hostname", "[expressionFullHostname]") {  // NOLINT
+  auto expr = expression::compile("${hostname('true')}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  REQUIRE("" != expr({flow_file_a}).asString());
+}
+
+TEST_CASE("UUID", "[expressionUuid]") {  // NOLINT
+  auto expr = expression::compile("${UUID()}");
+
+  auto flow_file_a = std::make_shared<MockFlowFile>();
+  REQUIRE(36 == expr({flow_file_a}).asString().length());
+}

Reply via email to