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()); +}
