blob: 61a82ba6d6ff8ccd182b61a22b0595f86bc471b5 [file] [log] [blame]
/**
*
* 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_PROPERTYVALUE_H_
#define LIBMINIFI_INCLUDE_CORE_PROPERTYVALUE_H_
#include <typeindex>
#include <string>
#include <utility>
#include <memory>
#include "CachedValueValidator.h"
#include "PropertyValidation.h"
#include "state/Value.h"
#include "TypedValues.h"
namespace org {
namespace apache {
namespace nifi {
namespace minifi {
namespace core {
static inline std::shared_ptr<state::response::Value> convert(const std::shared_ptr<state::response::Value> &prior, const std::string &ref) {
if (prior->getTypeIndex() == state::response::Value::UINT64_TYPE) {
// there are specializations, so check them first
if (std::dynamic_pointer_cast<TimePeriodValue>(prior)) {
return std::make_shared<TimePeriodValue>(ref);
} else if (std::dynamic_pointer_cast<DataSizeValue>(prior)) {
return std::make_shared<DataSizeValue>(ref);
} else {
return std::make_shared<state::response::UInt64Value>(ref);
}
} else if (prior->getTypeIndex() == state::response::Value::INT64_TYPE) {
return std::make_shared<state::response::Int64Value>(ref);
} else if (prior->getTypeIndex() == state::response::Value::UINT32_TYPE) {
return std::make_shared<state::response::UInt32Value>(ref);
} else if (prior->getTypeIndex() == state::response::Value::INT_TYPE) {
return std::make_shared<state::response::IntValue>(ref);
} else if (prior->getTypeIndex() == state::response::Value::BOOL_TYPE) {
return std::make_shared<state::response::BoolValue>(ref);
} else {
return std::make_shared<state::response::Value>(ref);
}
}
/**
* Purpose and Design: PropertyValue extends ValueNode, bringing with it value_.
* The reason for this is that there are additional features to support validation
* and value translation.
*/
class PropertyValue : public state::response::ValueNode {
using CachedValueValidator = internal::CachedValueValidator;
public:
PropertyValue()
: type_id(std::type_index(typeid(std::string))) {}
PropertyValue(const PropertyValue &o) = default;
PropertyValue(PropertyValue &&o) noexcept = default;
void setValidator(const gsl::not_null<std::shared_ptr<PropertyValidator>> &val) {
validator_ = val;
}
std::shared_ptr<PropertyValidator> getValidator() const {
return *validator_;
}
ValidationResult validate(const std::string &subject) const {
return validator_.validate(subject, getValue());
}
operator uint64_t() const {
return convertImpl<uint64_t>("uint64_t");
}
operator int64_t() const {
return convertImpl<int64_t>("int64_t");
}
operator uint32_t() const {
return convertImpl<uint32_t>("uint32_t");
}
operator int() const {
return convertImpl<int>("int");
}
operator bool() const {
return convertImpl<bool>("bool");
}
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;
}
/**
* Define the representations and eventual storage relationships through
* createValue
*/
template<typename T>
auto operator=(const T ref) -> typename std::enable_if<std::is_same<T, std::string>::value, PropertyValue&>::type {
validator_.invalidateCachedResult();
return WithAssignmentGuard(ref, [&] () -> PropertyValue& {
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;
} 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;
});
}
template<typename T>
auto operator=(const T ref) -> typename std::enable_if<std::is_same<T, int >::value ||
std::is_same<T, uint32_t >::value ||
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);
} else {
if (std::dynamic_pointer_cast<DataSizeValue>(value_)) {
value_ = std::make_shared<DataSizeValue>(ref);
type_id = DataSizeValue::type_id;
} else if (std::dynamic_pointer_cast<TimePeriodValue>(value_)) {
value_ = std::make_shared<TimePeriodValue>(ref);
type_id = TimePeriodValue::type_id;
} else if (type_id == std::type_index(typeid(T))) {
value_ = minifi::state::response::createValue(ref);
} 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 utils::internal::ConversionException("Assigning invalid types");
}
}
return *this;
}
template<typename T>
auto operator=(const T ref) -> typename std::enable_if<
std::is_same<T, char* >::value ||
std::is_same<T, const char* >::value, PropertyValue&>::type {
// translated these into strings
return operator=<std::string>(std::string(ref));
}
template<typename T>
auto operator=(const std::string &ref) -> typename std::enable_if<
std::is_same<T, DataSizeValue >::value ||
std::is_same<T, TimePeriodValue >::value, PropertyValue&>::type {
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;
CachedValueValidator validator_;
};
inline std::string conditional_conversion(const PropertyValue &v) {
return v.getValue()->getStringValue();
}
} // namespace core
} // namespace minifi
} // namespace nifi
} // namespace apache
} // namespace org
#endif // LIBMINIFI_INCLUDE_CORE_PROPERTYVALUE_H_