This is an automated email from the ASF dual-hosted git repository.
aboda pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/master by this push:
new 2b29b7b MINIFICPP-1231 - Early property value validation
MINIFICPP-1231 - Use property validators in MergeContent MINIFICPP-1231 - Fix
MergeContent allowed values.
2b29b7b is described below
commit 2b29b7b5440ae73c20defa91ac471cb44a0d2683
Author: Adam Debreceni <[email protected]>
AuthorDate: Wed May 27 11:03:51 2020 +0200
MINIFICPP-1231 - Early property value validation
MINIFICPP-1231 - Use property validators in MergeContent
MINIFICPP-1231 - Fix MergeContent allowed values.
Signed-off-by: Arpad Boda <[email protected]>
This closes #797
---
.../coap/controllerservice/CoapConnector.cpp | 2 +-
extensions/libarchive/MergeContent.cpp | 27 ++-
extensions/libarchive/MergeContent.h | 2 +-
.../tests/unit/ProcessorTests.cpp | 3 +-
libminifi/include/core/CachedValueValidator.h | 126 +++++++++++
libminifi/include/core/ConfigurableComponent.h | 27 ++-
libminifi/include/core/Property.h | 71 +++---
libminifi/include/core/PropertyValidation.h | 37 ++--
libminifi/include/core/PropertyValue.h | 143 +++++++------
libminifi/include/core/state/Value.h | 105 ++++-----
libminifi/include/utils/PropertyErrors.h | 93 ++++++++
libminifi/include/utils/ValueParser.h | 166 ++++++++++++++
libminifi/src/core/ConfigurableComponent.cpp | 68 +++---
libminifi/src/core/PropertyValidation.cpp | 24 +--
libminifi/test/archive-tests/MergeFileTests.cpp | 12 +-
libminifi/test/unit/PropertyValidationTests.cpp | 238 +++++++++++++++++++++
nanofi/include/blocks/comms.h | 8 +-
17 files changed, 912 insertions(+), 240 deletions(-)
diff --git a/extensions/coap/controllerservice/CoapConnector.cpp
b/extensions/coap/controllerservice/CoapConnector.cpp
index 1d152ff..31ff0a3 100644
--- a/extensions/coap/controllerservice/CoapConnector.cpp
+++ b/extensions/coap/controllerservice/CoapConnector.cpp
@@ -39,7 +39,7 @@ static core::Property RemoteServer;
static core::Property Port;
static core::Property MaxQueueSize;
-core::Property
CoapConnectorService::RemoteServer(core::PropertyBuilder::createProperty("Remote
Server")->withDescription("Remote CoAP server")->isRequired(true)->build());
+core::Property
CoapConnectorService::RemoteServer(core::PropertyBuilder::createProperty("Remote
Server")->withDescription("Remote CoAP server")->isRequired(false)->build());
core::Property CoapConnectorService::Port(
core::PropertyBuilder::createProperty("Remote
Port")->withDescription("Remote CoAP server
port")->withDefaultValue<uint64_t>(8181)->isRequired(true)->build());
core::Property CoapConnectorService::MaxQueueSize(
diff --git a/extensions/libarchive/MergeContent.cpp
b/extensions/libarchive/MergeContent.cpp
index 8bd3f79..137a872 100644
--- a/extensions/libarchive/MergeContent.cpp
+++ b/extensions/libarchive/MergeContent.cpp
@@ -39,14 +39,29 @@ namespace nifi {
namespace minifi {
namespace processors {
-core::Property MergeContent::MergeStrategy("Merge Strategy", "Defragment or
Bin-Packing Algorithm", MERGE_STRATEGY_DEFRAGMENT);
-core::Property MergeContent::MergeFormat("Merge Format", "Merge Format",
MERGE_FORMAT_CONCAT_VALUE);
+core::Property MergeContent::MergeStrategy(
+ core::PropertyBuilder::createProperty("Merge Strategy")
+ ->withDescription("Defragment or Bin-Packing Algorithm")
+ ->withAllowableValues<std::string>({MERGE_STRATEGY_DEFRAGMENT,
MERGE_STRATEGY_BIN_PACK})
+ ->withDefaultValue(MERGE_STRATEGY_DEFRAGMENT)->build());
+core::Property MergeContent::MergeFormat(
+ core::PropertyBuilder::createProperty("Merge Format")
+ ->withDescription("Merge Format")
+ ->withAllowableValues<std::string>({MERGE_FORMAT_CONCAT_VALUE,
MERGE_FORMAT_TAR_VALUE, MERGE_FORMAT_ZIP_VALUE})
+ ->withDefaultValue(MERGE_FORMAT_CONCAT_VALUE)->build());
core::Property MergeContent::CorrelationAttributeName("Correlation Attribute
Name", "Correlation Attribute Name", "");
-core::Property MergeContent::DelimiterStratgey("Delimiter Strategy",
"Determines if Header, Footer, and Demarcator should point to files",
DELIMITER_STRATEGY_FILENAME);
+core::Property MergeContent::DelimiterStrategy(
+ core::PropertyBuilder::createProperty("Delimiter Strategy")
+ ->withDescription("Determines if Header, Footer, and Demarcator should point
to files")
+ ->withAllowableValues<std::string>({DELIMITER_STRATEGY_FILENAME,
DELIMITER_STRATEGY_TEXT})
+ ->withDefaultValue(DELIMITER_STRATEGY_FILENAME)->build());
core::Property MergeContent::Header("Header File", "Filename specifying the
header to use", "");
core::Property MergeContent::Footer("Footer File", "Filename specifying the
footer to use", "");
core::Property MergeContent::Demarcator("Demarcator File", "Filename
specifying the demarcator to use", "");
-core::Property MergeContent::KeepPath("Keep Path", "If using the Zip or Tar
Merge Format, specifies whether or not the FlowFiles' paths should be included
in their entry", "false");
+core::Property MergeContent::KeepPath(
+ core::PropertyBuilder::createProperty("Keep Path")
+ ->withDescription("If using the Zip or Tar Merge Format, specifies whether
or not the FlowFiles' paths should be included in their entry")
+ ->withDefaultValue(false)->build());
core::Relationship MergeContent::Merge("merged", "The FlowFile containing the
merged content");
const char *BinaryConcatenationMerge::mimeType = "application/octet-stream";
const char *TarMerge::mimeType = "application/tar";
@@ -64,7 +79,7 @@ void MergeContent::initialize() {
properties.insert(MergeStrategy);
properties.insert(MergeFormat);
properties.insert(CorrelationAttributeName);
- properties.insert(DelimiterStratgey);
+ properties.insert(DelimiterStrategy);
properties.insert(Header);
properties.insert(Footer);
properties.insert(Demarcator);
@@ -106,7 +121,7 @@ void MergeContent::onSchedule(core::ProcessContext
*context, core::ProcessSessio
this->correlationAttributeName_ = value;
}
value = "";
- if (context->getProperty(DelimiterStratgey.getName(), value) &&
!value.empty()) {
+ if (context->getProperty(DelimiterStrategy.getName(), value) &&
!value.empty()) {
this->delimiterStratgey_ = value;
}
value = "";
diff --git a/extensions/libarchive/MergeContent.h
b/extensions/libarchive/MergeContent.h
index 40df40d..65066bc 100644
--- a/extensions/libarchive/MergeContent.h
+++ b/extensions/libarchive/MergeContent.h
@@ -282,7 +282,7 @@ class MergeContent : public processors::BinFiles {
static core::Property MergeStrategy;
static core::Property MergeFormat;
static core::Property CorrelationAttributeName;
- static core::Property DelimiterStratgey;
+ static core::Property DelimiterStrategy;
static core::Property KeepPath;
static core::Property Header;
static core::Property Footer;
diff --git a/extensions/standard-processors/tests/unit/ProcessorTests.cpp
b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
index 19eb414..f7dc551 100644
--- a/extensions/standard-processors/tests/unit/ProcessorTests.cpp
+++ b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
@@ -39,6 +39,7 @@
#include "core/ProcessSession.h"
#include "core/ProcessorNode.h"
#include "core/reporting/SiteToSiteProvenanceReportingTask.h"
+#include "utils/PropertyErrors.h"
TEST_CASE("Test Creation of GetFile", "[getfileCreate]") {
TestController testController;
@@ -341,7 +342,7 @@ TEST_CASE("LogAttributeTestInvalid", "[TestLogAttribute]") {
plan->setProperty(getfile,
org::apache::nifi::minifi::processors::GetFile::Directory.getName(), dir);
plan->setProperty(getfile,
org::apache::nifi::minifi::processors::GetFile::BatchSize.getName(), "1");
- REQUIRE_THROWS_AS(plan->setProperty(loggattr,
org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(),
"-1"), std::out_of_range);
+ REQUIRE_THROWS_AS(plan->setProperty(loggattr,
org::apache::nifi::minifi::processors::LogAttribute::FlowFilesToLog.getName(),
"-1"), utils::internal::ParseException);
LogTestController::getInstance().reset();
}
diff --git a/libminifi/include/core/CachedValueValidator.h
b/libminifi/include/core/CachedValueValidator.h
new file mode 100644
index 0000000..1076027
--- /dev/null
+++ b/libminifi/include/core/CachedValueValidator.h
@@ -0,0 +1,126 @@
+/**
+ *
+ * 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.
+ */
+
+#ifndef LIBMINIFI_INCLUDE_CORE_CACHEDVALUEVALIDATOR_H_
+#define LIBMINIFI_INCLUDE_CORE_CACHEDVALUEVALIDATOR_H_
+
+#include <utility>
+#include <memory>
+#include <string>
+#include "PropertyValidation.h"
+#include "state/Value.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace core {
+
+class PropertyValue;
+
+namespace internal {
+
+class CachedValueValidator {
+ friend class core::PropertyValue;
+
+ public:
+ enum class Result {
+ FAILURE,
+ SUCCESS,
+ RECOMPUTE
+ };
+
+ CachedValueValidator() = default;
+
+ CachedValueValidator(const CachedValueValidator& other) :
validator_(other.validator_) {}
+
+ CachedValueValidator(CachedValueValidator&& other) noexcept :
validator_(std::move(other.validator_)) {}
+
+ CachedValueValidator& operator=(const CachedValueValidator& other) {
+ if (this == &other) {
+ return *this;
+ }
+ setValidator(other.validator_);
+ return *this;
+ }
+
+ CachedValueValidator& operator=(CachedValueValidator&& other) {
+ if (this == &other) {
+ return *this;
+ }
+ setValidator(std::move(other.validator_));
+ return *this;
+ }
+
+ explicit CachedValueValidator(const std::shared_ptr<PropertyValidator>&
other) : validator_(other) {}
+
+ explicit CachedValueValidator(std::shared_ptr<PropertyValidator>&& other) :
validator_(std::move(other)) {}
+
+ CachedValueValidator& operator=(const
gsl::not_null<std::shared_ptr<PropertyValidator>>& new_validator) {
+ setValidator(new_validator);
+ return *this;
+ }
+
+ CachedValueValidator&
operator=(gsl::not_null<std::shared_ptr<PropertyValidator>>&& new_validator) {
+ setValidator(std::move(new_validator));
+ return *this;
+ }
+
+ const gsl::not_null<std::shared_ptr<PropertyValidator>>& operator*() const {
+ return validator_;
+ }
+
+ private:
+ template<typename T>
+ void setValidator(T&& newValidator) {
+ invalidateCachedResult();
+ validator_ = std::forward<T>(newValidator);
+ }
+
+ ValidationResult validate(const std::string& subject, const
std::shared_ptr<state::response::Value>& value) const {
+ if (validation_result_ == CachedValueValidator::Result::SUCCESS) {
+ return ValidationResult::Builder::createBuilder().isValid(true).build();
+ }
+ if (validation_result_ == CachedValueValidator::Result::FAILURE) {
+ return
ValidationResult::Builder::createBuilder().withSubject(subject).withInput(value->getStringValue()).isValid(false).build();
+ }
+ auto result = validator_->validate(subject, value);
+ if (result.valid()) {
+ validation_result_ = Result::SUCCESS;
+ } else {
+ validation_result_ = Result::FAILURE;
+ }
+ return result;
+ }
+
+ void invalidateCachedResult() {
+ validation_result_ = Result::RECOMPUTE;
+ }
+
+ gsl::not_null<std::shared_ptr<PropertyValidator>>
validator_{StandardValidators::VALID_VALIDATOR()};
+ mutable Result validation_result_{Result::RECOMPUTE};
+};
+
+} /* namespace internal */
+} /* namespace core */
+} /* namespace minifi */
+} /* namespace nifi */
+} /* namespace apache */
+} /* namespace org */
+
+#endif // LIBMINIFI_INCLUDE_CORE_CACHEDVALUEVALIDATOR_H_
diff --git a/libminifi/include/core/ConfigurableComponent.h
b/libminifi/include/core/ConfigurableComponent.h
index 794011f..61357aa 100644
--- a/libminifi/include/core/ConfigurableComponent.h
+++ b/libminifi/include/core/ConfigurableComponent.h
@@ -215,18 +215,23 @@ bool ConfigurableComponent::getProperty(const std::string
name, T &value) const
auto &&it = properties_.find(name);
if (it != properties_.end()) {
- Property item = it->second;
- value = static_cast<T>(item.getValue());
- if (item.getValue().getValue() != nullptr) {
- logger_->log_debug("Component %s property name %s value %s", name,
item.getName(), item.getValue().to_string());
- return true;
- } else {
- logger_->log_warn("Component %s property name %s, empty value", name,
item.getName());
- return false;
- }
+ const Property& item = it->second;
+ if (item.getValue().getValue() == nullptr) {
+ // empty value
+ if (item.getRequired()) {
+ logger_->log_error("Component %s required property %s is empty", name,
item.getName());
+ throw utils::internal::RequiredPropertyMissingException("Required
property is empty: " + item.getName());
+ }
+ logger_->log_debug("Component %s property name %s, empty value", name,
item.getName());
+ return false;
+ }
+ logger_->log_debug("Component %s property name %s value %s", name,
item.getName(), item.getValue().to_string());
+ // cast throws if the value is invalid
+ value = static_cast<T>(item.getValue());
+ return true;
} else {
- logger_->log_warn("Could not find property %s", name);
- return false;
+ logger_->log_warn("Could not find property %s", name);
+ return false;
}
}
diff --git a/libminifi/include/core/Property.h
b/libminifi/include/core/Property.h
index 142f784..01937e7 100644
--- a/libminifi/include/core/Property.h
+++ b/libminifi/include/core/Property.h
@@ -18,12 +18,9 @@
#ifndef LIBMINIFI_INCLUDE_CORE_PROPERTY_H_
#define LIBMINIFI_INCLUDE_CORE_PROPERTY_H_
-#include <math.h>
-#include <stdlib.h>
-
+#include <cmath>
+#include <cstdlib>
#include <algorithm>
-#include <atomic>
-#include <functional>
#include <map>
#include <memory>
#include <mutex>
@@ -35,11 +32,13 @@
#include <utility>
#include <vector>
+#include "CachedValueValidator.h"
#include "core/Core.h"
#include "PropertyValidation.h"
#include "PropertyValue.h"
#include "utils/StringUtils.h"
#include "utils/TimeUtil.h"
+#include "utils/gsl.h"
namespace org {
namespace apache {
@@ -53,6 +52,9 @@ class Property {
public:
/*!
* Create a new property
+ * Pay special attention to when value is "true" or "false"
+ * as they will get coerced to the bool true and false, causing
+ * further overwrites to inherit the bool validator.
*/
Property(std::string name, std::string description, std::string value, bool
is_required, std::string valid_regex, std::vector<std::string>
dependent_properties,
std::vector<std::pair<std::string, std::string>>
exclusive_of_properties)
@@ -84,9 +86,7 @@ class Property {
is_required_(false),
is_collection_(true),
supports_el_(false),
- is_transient_(false) {
- validator_ = StandardValidators::VALID;
- }
+ is_transient_(false) {}
Property(Property &&other) = default;
@@ -98,9 +98,7 @@ class Property {
is_required_(false),
is_collection_(false),
supports_el_(false),
- is_transient_(false) {
- validator_ = StandardValidators::VALID;
- }
+ is_transient_(false) {}
virtual ~Property() = default;
@@ -130,40 +128,33 @@ class Property {
template<typename T = std::string>
void setValue(const T &value) {
- PropertyValue vn = default_value_;
- vn = value;
- if (validator_) {
- vn.setValidator(validator_);
- ValidationResult result = validator_->validate(name_, vn.getValue());
- if (!result.valid()) {
- // throw some exception?
- }
- } else {
- vn.setValidator(core::StandardValidators::VALID);
- }
if (!is_collection_) {
values_.clear();
- values_.push_back(vn);
+ values_.push_back(default_value_);
} else {
- values_.push_back(vn);
+ values_.push_back(default_value_);
+ }
+ PropertyValue& vn = values_.back();
+ vn.setValidator(validator_);
+ vn = value;
+ ValidationResult result = vn.validate(name_);
+ if (!result.valid()) {
+ throw utils::internal::InvalidValueException(name_ + " value validation
failed");
}
}
- void setValue(PropertyValue &vn) {
- if (validator_) {
- vn.setValidator(validator_);
- ValidationResult result = validator_->validate(name_, vn.getValue());
- if (!result.valid()) {
- // throw some exception?
- }
- } else {
- vn.setValidator(core::StandardValidators::VALID);
- }
+ void setValue(PropertyValue &newValue) {
if (!is_collection_) {
values_.clear();
- values_.push_back(vn);
+ values_.push_back(newValue);
} else {
- values_.push_back(vn);
+ values_.push_back(newValue);
+ }
+ PropertyValue& vn = values_.back();
+ vn.setValidator(validator_);
+ ValidationResult result = vn.validate(name_);
+ if (!result.valid()) {
+ throw utils::internal::InvalidValueException(name_ + " value validation
failed");
}
}
void setSupportsExpressionLanguage(bool supportEl);
@@ -464,7 +455,7 @@ class Property {
validator_ = StandardValidators::getValidator(ret.getValue());
} else {
ret = value;
- validator_ = StandardValidators::VALID;
+ validator_ = StandardValidators::VALID_VALIDATOR();
}
return ret;
}
@@ -478,7 +469,7 @@ class Property {
bool is_collection_;
PropertyValue default_value_;
std::vector<PropertyValue> values_;
- std::shared_ptr<PropertyValidator> validator_;
+ gsl::not_null<std::shared_ptr<PropertyValidator>>
validator_{StandardValidators::VALID_VALIDATOR()};
std::string display_name_;
std::vector<PropertyValue> allowed_values_;
// types represents the allowable types for this property
@@ -529,8 +520,8 @@ class PropertyBuilder : public
std::enable_shared_from_this<PropertyBuilder> {
prop.default_value_ = df;
if (validator != nullptr) {
- prop.default_value_.setValidator(validator);
- prop.validator_ = validator;
+ prop.default_value_.setValidator(gsl::make_not_null(validator));
+ prop.validator_ = gsl::make_not_null(validator);
} else {
prop.validator_ =
StandardValidators::getValidator(prop.default_value_.getValue());
prop.default_value_.setValidator(prop.validator_);
diff --git a/libminifi/include/core/PropertyValidation.h
b/libminifi/include/core/PropertyValidation.h
index 2847369..2f1e593 100644
--- a/libminifi/include/core/PropertyValidation.h
+++ b/libminifi/include/core/PropertyValidation.h
@@ -315,16 +315,12 @@ class TimePeriodValidator : public PropertyValidator {
class StandardValidators {
public:
- static std::shared_ptr<PropertyValidator> VALID;
-
- static const std::shared_ptr<PropertyValidator> &getValidator(const
std::shared_ptr<minifi::state::response::Value> &input) {
+ static const gsl::not_null<std::shared_ptr<PropertyValidator>>
&getValidator(const std::shared_ptr<minifi::state::response::Value> &input) {
static StandardValidators init;
if (std::dynamic_pointer_cast<core::DataSizeValue>(input) != nullptr) {
return init.DATA_SIZE_VALIDATOR;
} else if (std::dynamic_pointer_cast<core::TimePeriodValue>(input) !=
nullptr) {
return init.TIME_PERIOD_VALIDATOR;
- } else if (std::dynamic_pointer_cast<core::DataSizeValue>(input) !=
nullptr) {
- return init.DATA_SIZE_VALIDATOR;
} else if
(std::dynamic_pointer_cast<minifi::state::response::BoolValue>(input) !=
nullptr) {
return init.BOOLEAN_VALIDATOR;
} else if
(std::dynamic_pointer_cast<minifi::state::response::IntValue>(input) !=
nullptr) {
@@ -336,29 +332,34 @@ class StandardValidators {
} else if
(std::dynamic_pointer_cast<minifi::state::response::UInt64Value>(input) !=
nullptr) {
return init.UNSIGNED_LONG_VALIDATOR;
} else {
- return org::apache::nifi::minifi::core::StandardValidators::VALID;
+ return
org::apache::nifi::minifi::core::StandardValidators::VALID_VALIDATOR();
}
}
- static std::shared_ptr<PropertyValidator> PORT_VALIDATOR() {
- static std::shared_ptr<PropertyValidator> validator =
std::make_shared<PortValidator>("PORT_VALIDATOR");
+ static const gsl::not_null<std::shared_ptr<PropertyValidator>>&
VALID_VALIDATOR() {
+ static gsl::not_null<std::shared_ptr<PropertyValidator>>
validator(std::make_shared<AlwaysValid>(true, "VALID"));
+ return validator;
+ }
+
+ static gsl::not_null<std::shared_ptr<PropertyValidator>> PORT_VALIDATOR() {
+ static gsl::not_null<std::shared_ptr<PropertyValidator>>
validator(std::make_shared<PortValidator>("PORT_VALIDATOR"));
return validator;
}
- static std::shared_ptr<PropertyValidator> LISTEN_PORT_VALIDATOR() {
- static std::shared_ptr<PropertyValidator> validator =
std::make_shared<ListenPortValidator>("PORT_VALIDATOR");
+ static gsl::not_null<std::shared_ptr<PropertyValidator>>
LISTEN_PORT_VALIDATOR() {
+ static gsl::not_null<std::shared_ptr<PropertyValidator>>
validator(std::make_shared<ListenPortValidator>("PORT_VALIDATOR"));
return validator;
}
private:
- std::shared_ptr<PropertyValidator> INVALID;
- std::shared_ptr<PropertyValidator> INTEGER_VALIDATOR;
- std::shared_ptr<PropertyValidator> UNSIGNED_INT_VALIDATOR;
- std::shared_ptr<PropertyValidator> LONG_VALIDATOR;
- std::shared_ptr<PropertyValidator> UNSIGNED_LONG_VALIDATOR;
- std::shared_ptr<PropertyValidator> BOOLEAN_VALIDATOR;
- std::shared_ptr<PropertyValidator> DATA_SIZE_VALIDATOR;
- std::shared_ptr<PropertyValidator> TIME_PERIOD_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> INVALID;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> INTEGER_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> UNSIGNED_INT_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> LONG_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> UNSIGNED_LONG_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> BOOLEAN_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> DATA_SIZE_VALIDATOR;
+ gsl::not_null<std::shared_ptr<PropertyValidator>> TIME_PERIOD_VALIDATOR;
StandardValidators();
};
diff --git a/libminifi/include/core/PropertyValue.h
b/libminifi/include/core/PropertyValue.h
index 97e6a4b..61a82ba 100644
--- a/libminifi/include/core/PropertyValue.h
+++ b/libminifi/include/core/PropertyValue.h
@@ -18,10 +18,12 @@
#ifndef LIBMINIFI_INCLUDE_CORE_PROPERTYVALUE_H_
#define LIBMINIFI_INCLUDE_CORE_PROPERTYVALUE_H_
-#include <memory>
-#include <string>
#include <typeindex>
+#include <string>
+#include <utility>
+#include <memory>
+#include "CachedValueValidator.h"
#include "PropertyValidation.h"
#include "state/Value.h"
#include "TypedValues.h"
@@ -61,78 +63,57 @@ static inline std::shared_ptr<state::response::Value>
convert(const std::shared_
* and value translation.
*/
class PropertyValue : public state::response::ValueNode {
+ using CachedValueValidator = internal::CachedValueValidator;
+
public:
PropertyValue()
- : type_id(std::type_index(typeid(std::string))) {
- validator_ = StandardValidators::VALID;
- }
+ : type_id(std::type_index(typeid(std::string))) {}
PropertyValue(const PropertyValue &o) = default;
PropertyValue(PropertyValue &&o) noexcept = default;
- void setValidator(const std::shared_ptr<PropertyValidator> &val) {
+ void setValidator(const gsl::not_null<std::shared_ptr<PropertyValidator>>
&val) {
validator_ = val;
}
std::shared_ptr<PropertyValidator> getValidator() const {
- return validator_;
+ return *validator_;
}
ValidationResult validate(const std::string &subject) const {
- if (validator_) {
- return validator_->validate(subject, getValue());
- } else {
- return ValidationResult::Builder::createBuilder().isValid(true).build();
- }
+ return validator_.validate(subject, getValue());
}
operator uint64_t() const {
- uint64_t res;
- if (value_->convertValue(res)) {
- return res;
- }
- throw std::runtime_error("Invalid conversion to uint64_t for" +
value_->getStringValue());
+ return convertImpl<uint64_t>("uint64_t");
}
operator int64_t() const {
- int64_t res;
- if (value_->convertValue(res)) {
- return res;
- }
- throw std::runtime_error("Invalid conversion to int64_t");
+ return convertImpl<int64_t>("int64_t");
}
operator uint32_t() const {
- uint32_t res;
- if (value_->convertValue(res)) {
- return res;
- }
- throw std::runtime_error("Invalid conversion to uint32_t for" +
value_->getStringValue());
+ return convertImpl<uint32_t>("uint32_t");
}
operator int() const {
- int res;
- if (value_->convertValue(res)) {
- return res;
- }
- throw std::runtime_error("Invalid conversion to int ");
+ return convertImpl<int>("int");
}
operator bool() const {
- bool res;
- if (value_->convertValue(res)) {
- return res;
- }
- throw std::runtime_error("Invalid conversion to bool");
+ return convertImpl<bool>("bool");
}
- PropertyValue &operator=(PropertyValue &&o) = default;
- PropertyValue &operator=(const PropertyValue &o) = default;
-
operator std::string() const {
+ if (!isValueUsable()) {
+ throw utils::internal::InvalidValueException("Cannot convert invalid
value");
+ }
return to_string();
}
+ PropertyValue &operator=(PropertyValue &&o) = default;
+ PropertyValue &operator=(const PropertyValue &o) = default;
+
std::type_index getTypeInfo() const {
return type_id;
}
@@ -142,25 +123,28 @@ class PropertyValue : public state::response::ValueNode {
*/
template<typename T>
auto operator=(const T ref) -> typename std::enable_if<std::is_same<T,
std::string>::value, PropertyValue&>::type {
- if (value_ == nullptr) {
- type_id = std::type_index(typeid(T));
- value_ = minifi::state::response::createValue(ref);
- } else {
- type_id = std::type_index(typeid(T));
- auto ret = convert(value_, ref);
- if (ret != nullptr) {
- value_ = ret;
+ validator_.invalidateCachedResult();
+ return WithAssignmentGuard(ref, [&] () -> PropertyValue& {
+ if (value_ == nullptr) {
+ type_id = std::type_index(typeid(T));
+ value_ = minifi::state::response::createValue(ref);
} else {
- /**
- * This is a protection mechanism that allows us to fail properties
that are strictly defined.
- * To maintain backwards compatibility we allow strings to be set by
way of the internal API
- * We then rely on the developer of the processor to perform the
conversion. We want to get away from
- * this, so this exception will throw an exception, forcefully, when
they specify types in properties.
- */
- throw std::runtime_error("Invalid conversion");
+ type_id = std::type_index(typeid(T));
+ auto ret = convert(value_, ref);
+ if (ret != nullptr) {
+ value_ = ret;
+ } else {
+ /**
+ * This is a protection mechanism that allows us to fail properties
that are strictly defined.
+ * To maintain backwards compatibility we allow strings to be set by
way of the internal API
+ * We then rely on the developer of the processor to perform the
conversion. We want to get away from
+ * this, so this exception will throw an exception, forcefully, when
they specify types in properties.
+ */
+ throw utils::internal::ConversionException("Invalid conversion");
+ }
}
- }
- return *this;
+ return *this;
+ });
}
template<typename T>
@@ -169,6 +153,7 @@ class PropertyValue : public state::response::ValueNode {
std::is_same<T, uint64_t >::value ||
std::is_same<T, int64_t >::value ||
std::is_same<T, bool >::value, PropertyValue&>::type {
+ validator_.invalidateCachedResult();
if (value_ == nullptr) {
type_id = std::type_index(typeid(T));
value_ = minifi::state::response::createValue(ref);
@@ -184,7 +169,7 @@ class PropertyValue : public state::response::ValueNode {
} else {
// this is not the place to perform translation. There are other
places within
// the code where we can do assignments and transformations from "10"
to (int)10;
- throw std::runtime_error("Assigning invalid types");
+ throw utils::internal::ConversionException("Assigning invalid types");
}
}
return *this;
@@ -202,14 +187,48 @@ class PropertyValue : public state::response::ValueNode {
auto operator=(const std::string &ref) -> typename std::enable_if<
std::is_same<T, DataSizeValue >::value ||
std::is_same<T, TimePeriodValue >::value, PropertyValue&>::type {
- value_ = std::make_shared<T>(ref);
- type_id = value_->getTypeIndex();
- return *this;
+ validator_.invalidateCachedResult();
+ return WithAssignmentGuard(ref, [&] () -> PropertyValue& {
+ value_ = std::make_shared<T>(ref);
+ type_id = value_->getTypeIndex();
+ return *this;
+ });
+ }
+
+ private:
+ template<typename T>
+ T convertImpl(const char* const type_name) const {
+ if (!isValueUsable()) {
+ throw utils::internal::InvalidValueException("Cannot convert invalid
value");
+ }
+ T res;
+ if (value_->convertValue(res)) {
+ return res;
+ }
+ throw utils::internal::ConversionException(std::string("Invalid conversion
to ") + type_name + " for " + value_->getStringValue());
+ }
+
+ bool isValueUsable() const {
+ if (!value_) return false;
+ return validate("__unknown__").valid();
+ }
+
+ template<typename Fn>
+ auto WithAssignmentGuard(const std::string& ref, Fn&& functor) ->
decltype(std::forward<Fn>(functor)()) {
+ // TODO(adebreceni): as soon as c++17 comes jump to a RAII implementation
+ // as we will need std::uncaught_exceptions()
+ try {
+ return std::forward<Fn>(functor)();
+ } catch(const utils::internal::ValueException&) {
+ type_id = std::type_index(typeid(std::string));
+ value_ = minifi::state::response::createValue(ref);
+ throw;
+ }
}
protected:
std::type_index type_id;
- std::shared_ptr<PropertyValidator> validator_;
+ CachedValueValidator validator_;
};
inline std::string conditional_conversion(const PropertyValue &v) {
diff --git a/libminifi/include/core/state/Value.h
b/libminifi/include/core/state/Value.h
index 162e269..f2b42c7 100644
--- a/libminifi/include/core/state/Value.h
+++ b/libminifi/include/core/state/Value.h
@@ -26,6 +26,7 @@
#include <string>
#include <vector>
#include <typeinfo>
+#include "utils/ValueParser.h"
namespace org {
namespace apache {
@@ -42,6 +43,8 @@ namespace response {
* representation is needed.
*/
class Value {
+ using ParseException = utils::internal::ParseException;
+
public:
explicit Value(const std::string &value)
: string_value(value),
@@ -85,35 +88,57 @@ class Value {
}
virtual bool getValue(uint32_t &ref) {
- const auto negative = string_value.find_first_of('-') != std::string::npos;
- if (negative) {
- return false;
- }
- ref = std::stoul(string_value);
+ try {
+ uint32_t value;
+ utils::internal::ValueParser(string_value).parse(value).parseEnd();
+ ref = value;
+ } catch(const ParseException&) {
+ return false;
+ }
return true;
}
virtual bool getValue(int &ref) {
- ref = std::stol(string_value);
+ try {
+ int value;
+ utils::internal::ValueParser(string_value).parse(value).parseEnd();
+ ref = value;
+ } catch(const ParseException&) {
+ return false;
+ }
return true;
}
virtual bool getValue(int64_t &ref) {
- ref = std::stoll(string_value);
+ try {
+ int64_t value;
+ utils::internal::ValueParser(string_value).parse(value).parseEnd();
+ ref = value;
+ } catch(const ParseException&) {
+ return false;
+ }
return true;
}
virtual bool getValue(uint64_t &ref) {
- const auto negative = string_value.find_first_of('-') != std::string::npos;
- if (negative) {
- return false;
- }
- ref = std::stoull(string_value);
+ try {
+ uint64_t value;
+ utils::internal::ValueParser(string_value).parse(value).parseEnd();
+ ref = value;
+ } catch(const ParseException&) {
+ return false;
+ }
return true;
}
virtual bool getValue(bool &ref) {
- std::istringstream(string_value) >> std::boolalpha >> ref;
+ try {
+ bool value;
+ utils::internal::ValueParser(string_value).parse(value).parseEnd();
+ ref = value;
+ } catch(const ParseException&) {
+ return false;
+ }
return true;
}
@@ -130,17 +155,8 @@ class UInt32Value : public Value {
}
explicit UInt32Value(const std::string &strvalue)
- : Value(strvalue),
- value(std::stoul(strvalue)) {
- /**
- * This is a fundamental change in that we would be changing where this
error occurs.
- * We should be prudent about breaking backwards compatibility, but since
Uint32Value
- * is only created with a validator and type, we **should** be okay.
- */
- const auto negative = strvalue.find_first_of('-') != std::string::npos;
- if (negative) {
- throw std::out_of_range("negative value detected");
- }
+ : Value(strvalue) {
+ utils::internal::ValueParser(strvalue).parse(value).parseEnd();
setTypeId<uint32_t>();
}
@@ -188,8 +204,8 @@ class IntValue : public Value {
}
explicit IntValue(const std::string &strvalue)
- : Value(strvalue),
- value(std::stoi(strvalue)) {
+ : Value(strvalue) {
+ utils::internal::ValueParser(strvalue).parse(value).parseEnd();
}
int getValue() const {
return value;
@@ -202,11 +218,9 @@ class IntValue : public Value {
}
virtual bool getValue(uint32_t &ref) {
- if (value >= 0) {
- ref = value;
- return true;
- }
- return false;
+ if (value < 0) return false;
+ ref = value;
+ return true;
}
virtual bool getValue(int64_t &ref) {
@@ -236,9 +250,7 @@ class BoolValue : public Value {
explicit BoolValue(const std::string &strvalue)
: Value(strvalue) {
- bool l;
- std::istringstream(strvalue) >> std::boolalpha >> l;
- value = l; // avoid warnings
+ utils::internal::ValueParser(strvalue).parse(value).parseEnd();
}
bool getValue() const {
@@ -289,17 +301,8 @@ class UInt64Value : public Value {
}
explicit UInt64Value(const std::string &strvalue)
- : Value(strvalue),
- value(std::stoull(strvalue)) {
- /**
- * This is a fundamental change in that we would be changing where this
error occurs.
- * We should be prudent about breaking backwards compatibility, but since
Uint64Value
- * is only created with a validator and type, we **should** be okay.
- */
- const auto negative = strvalue.find_first_of('-') != std::string::npos;
- if (negative) {
- throw std::out_of_range("negative value detected");
- }
+ : Value(strvalue) {
+ utils::internal::ValueParser(strvalue).parse(value).parseEnd();
setTypeId<uint64_t>();
}
@@ -344,8 +347,8 @@ class Int64Value : public Value {
setTypeId<int64_t>();
}
explicit Int64Value(const std::string &strvalue)
- : Value(strvalue),
- value(std::stoll(strvalue)) {
+ : Value(strvalue) {
+ utils::internal::ValueParser(strvalue).parse(value).parseEnd();
setTypeId<int64_t>();
}
@@ -368,11 +371,9 @@ class Int64Value : public Value {
}
virtual bool getValue(uint64_t &ref) {
- if (value >= 0) {
- ref = value;
- return true;
- }
- return false;
+ if (value < 0) return false;
+ ref = value;
+ return true;
}
virtual bool getValue(bool &ref) {
diff --git a/libminifi/include/utils/PropertyErrors.h
b/libminifi/include/utils/PropertyErrors.h
new file mode 100644
index 0000000..85474a5
--- /dev/null
+++ b/libminifi/include/utils/PropertyErrors.h
@@ -0,0 +1,93 @@
+/**
+ *
+ * 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/licenseas/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.
+ */
+
+#ifndef LIBMINIFI_INCLUDE_UTILS_PROPERTYERRORS_H_
+#define LIBMINIFI_INCLUDE_UTILS_PROPERTYERRORS_H_
+
+#include <string>
+
+#include "Exception.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+
+namespace core {
+
+class PropertyValue;
+class ConfigurableComponent;
+class Property;
+
+} /* namespace core */
+
+namespace utils {
+namespace internal {
+
+class ValueException : public Exception {
+ public:
+ explicit ValueException(const std::string& err) :
Exception(ExceptionType::GENERAL_EXCEPTION, err) {}
+ explicit ValueException(const char* err) :
Exception(ExceptionType::GENERAL_EXCEPTION, err) {}
+
+ // base class already has a virtual destructor
+};
+
+class PropertyException : public Exception {
+ public:
+ explicit PropertyException(const std::string& err) :
Exception(ExceptionType::GENERAL_EXCEPTION, err) {}
+ explicit PropertyException(const char* err) :
Exception(ExceptionType::GENERAL_EXCEPTION, err) {}
+
+ // base class already has a virtual destructor
+};
+
+/**
+ * Thrown during converting from and to Value
+ */
+class ConversionException : public ValueException {
+ using ValueException::ValueException;
+};
+
+/**
+ * Represents std::string -> Value conversion errors
+ */
+class ParseException : public ConversionException {
+ using ConversionException::ConversionException;
+};
+
+/**
+ * Thrown when trying to access invalid Values.
+ */
+class InvalidValueException : public ValueException {
+ using ValueException::ValueException;
+};
+
+/**
+ * When querying missing properties marked required.
+ */
+class RequiredPropertyMissingException : public PropertyException {
+ using PropertyException::PropertyException;
+};
+
+} /* namespace internal */
+} /* namespace utils */
+} /* namespace minifi */
+} /* namespace nifi */
+} /* namespace apache */
+} /* namespace org */
+
+#endif // LIBMINIFI_INCLUDE_UTILS_PROPERTYERRORS_H_
diff --git a/libminifi/include/utils/ValueParser.h
b/libminifi/include/utils/ValueParser.h
new file mode 100644
index 0000000..dfca9bc
--- /dev/null
+++ b/libminifi/include/utils/ValueParser.h
@@ -0,0 +1,166 @@
+/**
+ *
+ * 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/licenseas/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.
+ */
+
+#ifndef LIBMINIFI_INCLUDE_UTILS_VALUEPARSER_H_
+#define LIBMINIFI_INCLUDE_UTILS_VALUEPARSER_H_
+
+#include <exception>
+#include <string>
+#include <cstring>
+#include <vector>
+#include <cstdlib>
+#include <type_traits>
+#include <limits>
+
+#include "PropertyErrors.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace utils {
+namespace internal {
+
+class ValueParser {
+ public:
+ explicit ValueParser(const std::string& str, std::size_t offset = 0) :
str(str), offset(offset) {}
+
+ ValueParser& parse(int& out) { // NOLINT
+ long result; // NOLINT
+ auto len = safeCallConverter(std::strtol, result);
+ if (len == 0) {
+ throw ParseException("Couldn't parse int");
+ }
+ if (result < (std::numeric_limits<int>::min)() || result >
(std::numeric_limits<int>::max)()) {
+ throw ParseException("Cannot convert long to int");
+ }
+ offset += len;
+ out = result;
+ return *this;
+ }
+
+ ValueParser& parse(int64_t& out) {
+ long long result; // NOLINT
+ auto len = safeCallConverter(std::strtoll, result);
+ if (len == 0) {
+ throw ParseException("Couldn't parse long long");
+ }
+ if (result < (std::numeric_limits<int64_t>::min)() || result >
(std::numeric_limits<int64_t>::max)()) {
+ throw ParseException("Cannot convert long long to int64_t");
+ }
+ offset += len;
+ out = result;
+ return *this;
+ }
+
+ ValueParser& parse(uint32_t & out) {
+ skipWhitespace();
+ if (offset < str.length() && str[offset] == '-') {
+ throw ParseException("Not an unsigned long");
+ }
+ unsigned long result; // NOLINT
+ auto len = safeCallConverter(std::strtoul, result);
+ if (len == 0) {
+ throw ParseException("Couldn't parse uint32_t");
+ }
+ if (result > (std::numeric_limits<uint32_t>::max)()) {
+ throw ParseException("Cannot convert unsigned long to uint32_t");
+ }
+ offset += len;
+ out = result;
+ return *this;
+ }
+
+ ValueParser& parse(uint64_t& out) {
+ skipWhitespace();
+ if (offset < str.length() && str[offset] == '-') {
+ throw ParseException("Not an unsigned long");
+ }
+ unsigned long long result; // NOLINT
+ auto len = safeCallConverter(std::strtoull, result);
+ if (len == 0) {
+ throw ParseException("Couldn't parse unsigned long long");
+ }
+ if (result > (std::numeric_limits<uint64_t>::max)()) {
+ throw ParseException("Cannot convert unsigned long long to uint64_t");
+ }
+ offset += len;
+ out = result;
+ return *this;
+ }
+
+ ValueParser& parse(bool& out) {
+ skipWhitespace();
+ if (std::strncmp(str.c_str() + offset, "false", std::strlen("false")) ==
0) {
+ offset += std::strlen("false");
+ out = false;
+ } else if (std::strncmp(str.c_str() + offset, "true", std::strlen("true"))
== 0) {
+ offset += std::strlen("true");
+ out = true;
+ } else {
+ throw ParseException("Couldn't parse bool");
+ }
+ return *this;
+ }
+
+ void parseEnd() {
+ skipWhitespace();
+ if (offset < str.length()) {
+ throw ParseException("Expected to parse till the end");
+ }
+ }
+
+ private:
+ /**
+ *
+ * @tparam T
+ * @param converter
+ * @param out
+ * @return the number of characters used during conversion, 0 for error
+ */
+ template<typename T>
+ std::size_t safeCallConverter(T (*converter)(const char* begin, char** end,
int base), T& out) {
+ const char* const begin = str.c_str() + offset;
+ char* end;
+ errno = 0;
+ T result = converter(begin, &end, 10);
+ if (end == begin || errno == ERANGE) {
+ return 0;
+ }
+ out = result;
+ return end - begin;
+ }
+
+ void skipWhitespace() {
+ while (offset < str.length() && std::isspace(str[offset])) {
+ ++offset;
+ }
+ }
+
+ const std::string& str;
+ std::size_t offset;
+};
+
+} /* namespace internal */
+} /* namespace utils */
+} /* namespace minifi */
+} /* namespace nifi */
+} /* namespace apache */
+} /* namespace org */
+
+#endif // LIBMINIFI_INCLUDE_UTILS_VALUEPARSER_H_
diff --git a/libminifi/src/core/ConfigurableComponent.cpp
b/libminifi/src/core/ConfigurableComponent.cpp
index 0f29f05..20dbaa2 100644
--- a/libminifi/src/core/ConfigurableComponent.cpp
+++ b/libminifi/src/core/ConfigurableComponent.cpp
@@ -24,6 +24,7 @@
#include "core/ConfigurableComponent.h"
#include "core/logging/LoggerConfiguration.h"
+#include "utils/gsl.h"
namespace org {
namespace apache {
@@ -66,15 +67,16 @@ bool ConfigurableComponent::getProperty(const std::string
&name, Property &prop)
*/
bool ConfigurableComponent::setProperty(const std::string name, std::string
value) {
std::lock_guard<std::mutex> lock(configuration_mutex_);
- auto &&it = properties_.find(name);
+ auto it = properties_.find(name);
if (it != properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&]{
+ onPropertyModified(orig_property, new_property);
+ logger_->log_debug("Component %s property name %s value %s", name,
new_property.getName(), value);
+ });
new_property.setValue(value);
- properties_[new_property.getName()] = new_property;
- onPropertyModified(orig_property, new_property);
- logger_->log_debug("Component %s property name %s value %s", name,
new_property.getName(), value);
return true;
} else {
if (accept_all_properties_) {
@@ -102,11 +104,12 @@ bool ConfigurableComponent::updateProperty(const
std::string &name, const std::s
if (it != properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&] {
+ onPropertyModified(orig_property, new_property);
+ logger_->log_debug("Component %s property name %s value %s", name,
new_property.getName(), value);
+ });
new_property.addValue(value);
- properties_[new_property.getName()] = new_property;
- onPropertyModified(orig_property, new_property);
- logger_->log_debug("Component %s property name %s value %s", name,
new_property.getName(), value);
return true;
} else {
return false;
@@ -125,11 +128,12 @@ bool ConfigurableComponent::setProperty(Property &prop,
std::string value) {
if (it != properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&] {
+ onPropertyModified(orig_property, new_property);
+ logger_->log_debug("property name %s value %s and new value is %s",
prop.getName(), value, new_property.getValue().to_string());
+ });
new_property.setValue(value);
- properties_[new_property.getName()] = new_property;
- onPropertyModified(orig_property, new_property);
- logger_->log_debug("property name %s value %s and new value is %s",
prop.getName(), value, new_property.getValue().to_string());
return true;
} else {
if (accept_all_properties_) {
@@ -153,11 +157,12 @@ bool ConfigurableComponent::setProperty(Property &prop,
PropertyValue &value) {
if (it != properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&] {
+ onPropertyModified(orig_property, new_property);
+ logger_->log_debug("property name %s value %s and new value is %s",
prop.getName(), new_property.getName(), value,
new_property.getValue().to_string());
+ });
new_property.setValue(value);
- properties_[new_property.getName()] = new_property;
- onPropertyModified(orig_property, new_property);
- logger_->log_debug("property name %s value %s and new value is %s",
prop.getName(), new_property.getName(), value,
new_property.getValue().to_string());
return true;
} else {
if (accept_all_properties_) {
@@ -212,7 +217,16 @@ bool ConfigurableComponent::getDynamicProperty(const
std::string name, std::stri
auto &&it = dynamic_properties_.find(name);
if (it != dynamic_properties_.end()) {
- Property item = it->second;
+ const Property& item = it->second;
+ if (item.getValue().getValue() == nullptr) {
+ // empty property value
+ if (item.getRequired()) {
+ logger_->log_error("Component %s required dynamic property %s is
empty", name, item.getName());
+ throw std::runtime_error("Required dynamic property is empty: " +
item.getName());
+ }
+ logger_->log_debug("Component %s dynamic property name %s, empty value",
name, item.getName());
+ return false;
+ }
value = item.getValue().to_string();
logger_->log_debug("Component %s dynamic property name %s value %s", name,
item.getName(), value);
return true;
@@ -243,12 +257,13 @@ bool ConfigurableComponent::setDynamicProperty(const
std::string name, std::stri
if (it != dynamic_properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&] {
+ onDynamicPropertyModified(orig_property, new_property);
+ logger_->log_debug("Component %s dynamic property name %s value %s",
name, new_property.getName(), value);
+ });
new_property.setValue(value);
new_property.setSupportsExpressionLanguage(true);
- dynamic_properties_[new_property.getName()] = new_property;
- onDynamicPropertyModified(orig_property, new_property);
- logger_->log_debug("Component %s dynamic property name %s value %s", name,
new_property.getName(), value);
return true;
} else {
return createDynamicProperty(name, value);
@@ -261,12 +276,13 @@ bool ConfigurableComponent::updateDynamicProperty(const
std::string &name, const
if (it != dynamic_properties_.end()) {
Property orig_property = it->second;
- Property new_property = orig_property;
+ Property& new_property = it->second;
+ auto onExit = gsl::finally([&] {
+ onDynamicPropertyModified(orig_property, new_property);
+ logger_->log_debug("Component %s dynamic property name %s value %s",
name, new_property.getName(), value);
+ });
new_property.addValue(value);
new_property.setSupportsExpressionLanguage(true);
- dynamic_properties_[new_property.getName()] = new_property;
- onDynamicPropertyModified(orig_property, new_property);
- logger_->log_debug("Component %s dynamic property name %s value %s", name,
new_property.getName(), value);
return true;
} else {
return createDynamicProperty(name, value);
diff --git a/libminifi/src/core/PropertyValidation.cpp
b/libminifi/src/core/PropertyValidation.cpp
index 177a3c7..304aa39 100644
--- a/libminifi/src/core/PropertyValidation.cpp
+++ b/libminifi/src/core/PropertyValidation.cpp
@@ -23,20 +23,18 @@ namespace nifi {
namespace minifi {
namespace core {
-std::shared_ptr<PropertyValidator> StandardValidators::VALID =
std::make_shared<AlwaysValid>(true, "VALID");
+StandardValidators::StandardValidators()
+ : INVALID(std::make_shared<AlwaysValid>(false, "INVALID")),
+ INTEGER_VALIDATOR(std::make_shared<IntegerValidator>("INTEGER_VALIDATOR")),
+
UNSIGNED_INT_VALIDATOR(std::make_shared<UnsignedIntValidator>("NON_NEGATIVE_INTEGER_VALIDATOR")),
+ LONG_VALIDATOR(std::make_shared<LongValidator>("LONG_VALIDATOR")),
+ // name is used by java nifi validators, so we should keep this LONG and
not change to reflect
+ // its internal use
+
UNSIGNED_LONG_VALIDATOR(std::make_shared<UnsignedLongValidator>("LONG_VALIDATOR")),
+
DATA_SIZE_VALIDATOR(std::make_shared<DataSizeValidator>("DATA_SIZE_VALIDATOR")),
+
TIME_PERIOD_VALIDATOR(std::make_shared<TimePeriodValidator>("TIME_PERIOD_VALIDATOR")),
+ BOOLEAN_VALIDATOR(std::make_shared<BooleanValidator>("BOOLEAN_VALIDATOR"))
{}
-StandardValidators::StandardValidators() {
- INVALID = std::make_shared<AlwaysValid>(false, "INVALID");
- INTEGER_VALIDATOR = std::make_shared<IntegerValidator>("INTEGER_VALIDATOR");
- UNSIGNED_INT_VALIDATOR =
std::make_shared<UnsignedIntValidator>("NON_NEGATIVE_INTEGER_VALIDATOR");
- LONG_VALIDATOR = std::make_shared<LongValidator>("LONG_VALIDATOR");
- // name is used by java nifi validators, so we should keep this LONG and not
change to reflect
- // its internal use
- UNSIGNED_LONG_VALIDATOR =
std::make_shared<UnsignedLongValidator>("LONG_VALIDATOR");
- DATA_SIZE_VALIDATOR =
std::make_shared<DataSizeValidator>("DATA_SIZE_VALIDATOR");
- TIME_PERIOD_VALIDATOR =
std::make_shared<TimePeriodValidator>("TIME_PERIOD_VALIDATOR");
- BOOLEAN_VALIDATOR = std::make_shared<BooleanValidator>("BOOLEAN_VALIDATOR");
-}
} /* namespace core */
} /* namespace minifi */
} /* namespace nifi */
diff --git a/libminifi/test/archive-tests/MergeFileTests.cpp
b/libminifi/test/archive-tests/MergeFileTests.cpp
index a6e8315..e6920fb 100644
--- a/libminifi/test/archive-tests/MergeFileTests.cpp
+++ b/libminifi/test/archive-tests/MergeFileTests.cpp
@@ -224,7 +224,7 @@ TEST_CASE("MergeFileDefragment", "[mergefiletest1]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_CONCAT_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_DEFRAGMENT);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_TEXT);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_TEXT);
core::ProcessSession sessionGenFlowFile(context);
std::shared_ptr<core::FlowFile> record[6];
@@ -326,7 +326,7 @@ TEST_CASE("MergeFileDefragmentDelimiter",
"[mergefiletest2]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_CONCAT_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_DEFRAGMENT);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_FILENAME);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_FILENAME);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::Header,
HEADER_FILE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::Footer,
FOOTER_FILE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::Demarcator,
DEMARCATOR_FILE);
@@ -419,7 +419,7 @@ TEST_CASE("MergeFileDefragmentDropFlow",
"[mergefiletest3]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_CONCAT_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_DEFRAGMENT);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_TEXT);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_TEXT);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MaxBinAge,
"1 sec");
core::ProcessSession sessionGenFlowFile(context);
@@ -516,7 +516,7 @@ TEST_CASE("MergeFileBinPack", "[mergefiletest4]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_CONCAT_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_BIN_PACK);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_TEXT);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_TEXT);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MinSize,
"96");
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::CorrelationAttributeName,
"tag");
@@ -598,7 +598,7 @@ TEST_CASE("MergeFileTar", "[mergefiletest4]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_TAR_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_BIN_PACK);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_TEXT);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_TEXT);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MinSize,
"96");
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::CorrelationAttributeName,
"tag");
@@ -689,7 +689,7 @@ TEST_CASE("MergeFileZip", "[mergefiletest5]") {
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeFormat,
MERGE_FORMAT_ZIP_VALUE);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MergeStrategy,
MERGE_STRATEGY_BIN_PACK);
-
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStratgey,
DELIMITER_STRATEGY_TEXT);
+
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::DelimiterStrategy,
DELIMITER_STRATEGY_TEXT);
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::MinSize,
"96");
context->setProperty(org::apache::nifi::minifi::processors::MergeContent::CorrelationAttributeName,
"tag");
diff --git a/libminifi/test/unit/PropertyValidationTests.cpp
b/libminifi/test/unit/PropertyValidationTests.cpp
new file mode 100644
index 0000000..5a50164
--- /dev/null
+++ b/libminifi/test/unit/PropertyValidationTests.cpp
@@ -0,0 +1,238 @@
+/**
+ *
+ * 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.
+ */
+
+#include "../TestBase.h"
+#include "core/ConfigurableComponent.h"
+#include "utils/PropertyErrors.h"
+#include "core/PropertyValidation.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace core {
+
+using namespace utils::internal;
+/**
+ * This Tests checks a deprecated behavior that should be removed
+ * in the next major release.
+ */
+TEST_CASE("Some default values get coerced to typed variants") {
+ auto prop = Property("prop", "d", "true");
+ REQUIRE_THROWS_AS(prop.setValue("banana"), ConversionException&);
+
+ const std::string SPACE = " ";
+ auto prop2 = Property("prop", "d", SPACE + "true");
+ prop2.setValue("banana");
+}
+
+TEST_CASE("Converting invalid PropertyValue") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(0)
+ ->build();
+ REQUIRE_THROWS_AS(prop.setValue("not int"), ParseException&);
+ REQUIRE_THROWS_AS(static_cast<int>(prop.getValue()), InvalidValueException&);
+}
+
+TEST_CASE("Parsing int has baggage after") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(0)
+ ->build();
+ REQUIRE_THROWS_AS(prop.setValue("55almost int"), ParseException&);
+}
+
+TEST_CASE("Parsing int has spaces") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(0)
+ ->build();
+ prop.setValue(" 55 ");
+ REQUIRE(static_cast<int>(prop.getValue()) == 55);
+}
+
+TEST_CASE("Parsing int out of range") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(0)
+ ->build();
+ REQUIRE_THROWS_AS(prop.setValue(" 5000000000 "), ParseException&);
+}
+
+TEST_CASE("Parsing bool has baggage after") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<bool>(true)
+ ->build();
+ REQUIRE_THROWS_AS(prop.setValue("false almost bool"), ParseException&);
+}
+
+class TestConfigurableComponent : public ConfigurableComponent {
+ public:
+ bool supportsDynamicProperties() override {
+ return true;
+ }
+
+ bool canEdit() override {
+ return true;
+ }
+
+ void onPropertyModified(const Property &old_property, const Property
&new_property) override {
+ if (onPropertyModifiedCallback) onPropertyModifiedCallback(old_property,
new_property);
+ }
+
+ void onDynamicPropertyModified(const Property &old_property, const Property
&new_property) override {
+ if (onDynamicPropertyModifiedCallback)
onDynamicPropertyModifiedCallback(old_property, new_property);
+ }
+
+ template<typename Fn>
+ void setPropertyModifiedCallback(Fn &&functor) {
+ onPropertyModifiedCallback = std::forward<Fn>(functor);
+ }
+
+ template<typename Fn>
+ void setDynamicPropertyModifiedCallback(Fn &&functor) {
+ onDynamicPropertyModifiedCallback = std::forward<Fn>(functor);
+ }
+
+ private:
+ std::function<void(const Property &, const Property &)>
onPropertyModifiedCallback;
+ std::function<void(const Property &, const Property &)>
onDynamicPropertyModifiedCallback;
+};
+
+TEST_CASE("Missing Required With Default") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->isRequired(true)
+ ->withDefaultValue<std::string>("default")
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ std::string value;
+ REQUIRE(component.getProperty(prop.getName(), value));
+ REQUIRE(value == "default");
+}
+
+TEST_CASE("Missing Required Without Default") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->isRequired(true)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ std::string value;
+ REQUIRE_THROWS_AS(component.getProperty(prop.getName(), value),
RequiredPropertyMissingException&);
+}
+
+TEST_CASE("Missing Optional Without Default") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->isRequired(false)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ std::string value;
+ REQUIRE_FALSE(component.getProperty(prop.getName(), value));
+}
+
+TEST_CASE("Valid Optional Without Default") {
+ // without a default the value will be stored as a string
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->isRequired(false)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ component.setProperty(prop.getName(), "some data");
+ std::string value;
+ REQUIRE(component.getProperty(prop.getName(), value));
+ REQUIRE(value == "some data");
+}
+
+TEST_CASE("Invalid With Default") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<bool>(true)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ REQUIRE_THROWS_AS(component.setProperty("prop", "banana"), ParseException&);
+ std::string value;
+ REQUIRE_THROWS_AS(component.getProperty(prop.getName(), value),
InvalidValueException&);
+}
+
+TEST_CASE("Valid With Default") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(55)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ REQUIRE(component.setProperty("prop", "23"));
+ int value;
+ REQUIRE(component.getProperty(prop.getName(), value));
+ REQUIRE(value == 23);
+}
+
+TEST_CASE("Invalid conversion") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<std::string>("banana")
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ bool value;
+ REQUIRE_THROWS_AS(component.getProperty(prop.getName(), value),
ConversionException&);
+}
+
+TEST_CASE("Write Invalid Then Override With Valid") {
+ // we always base the assignment on the default value
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int>(55)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ REQUIRE_THROWS_AS(component.setProperty(prop.getName(), "banana"),
ConversionException&);
+ component.setProperty(prop.getName(), "98");
+ int value;
+ REQUIRE(component.getProperty(prop.getName(), value));
+ REQUIRE(value == 98);
+}
+
+TEST_CASE("Property Change notification gets called even on erroneous
assignment") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<bool>(true)
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ int callbackCount = 0;
+ component.setPropertyModifiedCallback([&](const Property &, const Property
&) {
+ ++callbackCount;
+ });
+ REQUIRE_THROWS_AS(component.setProperty(prop.getName(), "banana"),
ConversionException&);
+ REQUIRE(callbackCount == 1);
+}
+
+TEST_CASE("Correctly Typed Property With Invalid Validation") {
+ auto prop = PropertyBuilder::createProperty("prop")
+ ->withDefaultValue<int64_t>(5,
std::make_shared<LongValidator>("myValidator", 0, 10))
+ ->build();
+ TestConfigurableComponent component;
+ component.setSupportedProperties({prop});
+ int callbackCount = 0;
+ component.setPropertyModifiedCallback([&](const Property &, const Property
&) {
+ ++callbackCount;
+ });
+ REQUIRE_THROWS_AS(component.setProperty(prop.getName(), "20"),
InvalidValueException&);
+ REQUIRE(callbackCount == 1);
+}
+
+} /* namespace core */
+} /* namespace minifi */
+} /* namespace nifi */
+} /* namespace apache */
+} /* namespace org */
diff --git a/nanofi/include/blocks/comms.h b/nanofi/include/blocks/comms.h
index bfacc3d..818ef27 100644
--- a/nanofi/include/blocks/comms.h
+++ b/nanofi/include/blocks/comms.h
@@ -23,9 +23,11 @@
#include "../api/nanofi.h"
#include "core/processors.h"
-#define SUCCESS 0x00
-#define FINISHED_EARLY 0x01
-#define FAIL 0x02
+enum {
+ SUCCESS = 0x00,
+ FINISHED_EARLY = 0x01,
+ FAIL = 0x02
+};
typedef int transmission_stop(void *);