This is an automated email from the ASF dual-hosted git repository.
martinzink pushed a commit to branch enrichment_processors
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/enrichment_processors by this
push:
new 390299a8b review changes
390299a8b is described below
commit 390299a8bed3734f09c87acb72364e6aea3cc361
Author: Martin Zink <[email protected]>
AuthorDate: Mon Oct 19 08:00:33 2026 +0200
review changes
---
.../ubuntu_22_04_clang_arm_manifest.json | 2 +-
PROCESSORS.md | 2 +-
extension-framework/include/core/FlowFileStore.h | 4 +-
.../processors/ForkEnrichment.cpp | 7 ++-
.../processors/ForkEnrichment.h | 13 +++---
.../processors/JoinEnrichmentAttributes.cpp | 53 ++++++++++------------
.../processors/JoinEnrichmentAttributes.h | 16 +++----
.../tests/features/enrichment.feature | 15 ++++--
.../tests/unit/ForkEnrichmentTests.cpp | 8 ++--
.../tests/unit/JoinEnrichmentAttributesTests.cpp | 51 ++++++++++++++++++++-
.../standard-processors/utils/EnrichmentUtils.h | 11 +++++
11 files changed, 125 insertions(+), 57 deletions(-)
diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json
b/.github/references/ubuntu_22_04_clang_arm_manifest.json
index 01199f116..fbbb8a95b 100644
--- a/.github/references/ubuntu_22_04_clang_arm_manifest.json
+++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json
@@ -9962,7 +9962,7 @@
},
{
"name": "joined",
- "description": "The resultant FlowFile with
Records joined together from both the original and enrichment FlowFiles will be
routed to this relationship"
+ "description": "The resultant FlowFile with
attributes joined together from both the original and enrichment FlowFiles will
be routed to this relationship"
},
{
"name": "original",
diff --git a/PROCESSORS.md b/PROCESSORS.md
index 68487069c..3803226fe 100644
--- a/PROCESSORS.md
+++ b/PROCESSORS.md
@@ -1328,7 +1328,7 @@ In the list below, the names of required properties
appear in bold. Any other pr
| Name | Description
|
|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| invalid | Any FlowFiles without the requisite attributes will be routed
here
|
-| joined | The resultant FlowFile with Records joined together from both the
original and enrichment FlowFiles will be routed to this relationship
|
+| joined | The resultant FlowFile with attributes joined together from both
the original and enrichment FlowFiles will be routed to this relationship
|
| original | Both of the incoming FlowFiles ('original' and 'enrichment') will
be routed to this Relationship. I.e., this is the 'original' version of both of
these FlowFiles.
|
| timeout | If one of the incoming FlowFiles (i.e., the 'original' FlowFile
or the 'enrichment' FlowFile) arrives to this Processor but the other does not
arrive within the configured Timeout period, the FlowFile that did arrive is
routed to this relationship. |
diff --git a/extension-framework/include/core/FlowFileStore.h
b/extension-framework/include/core/FlowFileStore.h
index 94df5722f..b5d88148b 100644
--- a/extension-framework/include/core/FlowFileStore.h
+++ b/extension-framework/include/core/FlowFileStore.h
@@ -17,7 +17,9 @@
#pragma once
+#include <atomic>
#include <memory>
+#include <mutex>
#include <unordered_set>
#include <utility>
@@ -32,7 +34,7 @@ class FlowFileStore {
return {};
}
std::lock_guard<std::mutex> guard(flow_file_mutex_);
- return std::move(incoming_files_);
+ return std::exchange(incoming_files_, {});
}
void put(const std::shared_ptr<FlowFile>& flowFile) {
diff --git a/extensions/standard-processors/processors/ForkEnrichment.cpp
b/extensions/standard-processors/processors/ForkEnrichment.cpp
index 9cc3b34de..36e00b704 100644
--- a/extensions/standard-processors/processors/ForkEnrichment.cpp
+++ b/extensions/standard-processors/processors/ForkEnrichment.cpp
@@ -17,7 +17,10 @@
#include "ForkEnrichment.h"
+#include <string>
+
#include "core/Resource.h"
+#include "magic_enum/magic_enum.hpp"
#include "minifi-cpp/core/ProcessSession.h"
#include "utils/ProcessorConfigUtils.h"
@@ -42,8 +45,8 @@ void ForkEnrichment::onTrigger(core::ProcessContext&,
core::ProcessSession& sess
while (const auto original = session.get()) {
const auto enrichment = session.clone(*original);
- original->setAttribute(ENRICHMENT_ROLE, "ORIGINAL");
- enrichment->setAttribute(ENRICHMENT_ROLE, "ENRICHMENT");
+ original->setAttribute(ENRICHMENT_ROLE,
std::string{magic_enum::enum_name(EnrichmentRole::ORIGINAL)});
+ enrichment->setAttribute(ENRICHMENT_ROLE,
std::string{magic_enum::enum_name(EnrichmentRole::ENRICHMENT)});
const std::string group_id =
utils::IdGenerator::getIdGenerator()->generate().to_string();
original->setAttribute(ENRICHMENT_GROUP_ID, group_id);
diff --git a/extensions/standard-processors/processors/ForkEnrichment.h
b/extensions/standard-processors/processors/ForkEnrichment.h
index a8f103bfc..b14299df2 100644
--- a/extensions/standard-processors/processors/ForkEnrichment.h
+++ b/extensions/standard-processors/processors/ForkEnrichment.h
@@ -17,14 +17,14 @@
#pragma once
-#include <string>
+#include <array>
+#include <cstdint>
+#include <optional>
#include "core/ProcessorImpl.h"
#include "core/PropertyDefinitionBuilder.h"
#include "minifi-cpp/core/PropertyDefinition.h"
#include "utils/EnrichmentUtils.h"
-#include "utils/Enum.h"
-#include "utils/RegexUtils.h"
namespace org::apache::nifi::minifi::standard {
@@ -56,13 +56,14 @@ class ForkEnrichment : public core::ProcessorImpl {
EXTENSIONAPI static constexpr core::annotation::Input InputRequirement =
core::annotation::Input::INPUT_REQUIRED;
EXTENSIONAPI static constexpr bool IsSingleThreaded = false;
- EXTENSIONAPI static constexpr auto EnrichmentRole =
core::OutputAttributeDefinition<2>{
+ EXTENSIONAPI static constexpr auto EnrichmentRoleOutputAttribute =
core::OutputAttributeDefinition<2>{
ENRICHMENT_ROLE, {Enrichment, Original}, "The role to use for
enrichment. This will either be ORIGINAL or ENRICHMENT."};
- EXTENSIONAPI static constexpr auto EnrichmentGroupId =
core::OutputAttributeDefinition<2>{ENRICHMENT_GROUP_ID,
+ EXTENSIONAPI static constexpr auto EnrichmentGroupIdOutputAttribute =
core::OutputAttributeDefinition<2>{ENRICHMENT_GROUP_ID,
{Enrichment, Original},
"The Group ID to use in order to correlate the 'original' FlowFile with
the 'enrichment' FlowFile."};
- EXTENSIONAPI static constexpr auto OutputAttributes =
std::array<core::OutputAttributeReference, 2>{EnrichmentRole,
EnrichmentGroupId};
+ EXTENSIONAPI static constexpr auto OutputAttributes =
+ std::array<core::OutputAttributeReference,
2>{EnrichmentRoleOutputAttribute, EnrichmentGroupIdOutputAttribute};
ADD_COMMON_VIRTUAL_FUNCTIONS_FOR_PROCESSORS
diff --git
a/extensions/standard-processors/processors/JoinEnrichmentAttributes.cpp
b/extensions/standard-processors/processors/JoinEnrichmentAttributes.cpp
index 9a616e1c8..84d56d644 100644
--- a/extensions/standard-processors/processors/JoinEnrichmentAttributes.cpp
+++ b/extensions/standard-processors/processors/JoinEnrichmentAttributes.cpp
@@ -16,9 +16,12 @@
*/
#include "JoinEnrichmentAttributes.h"
+#include <string>
+#include <utility>
+
#include "core/Resource.h"
+#include "magic_enum/magic_enum.hpp"
#include "minifi-cpp/core/ProcessSession.h"
-#include "utils/AttributeErrors.h"
#include "utils/EnrichmentUtils.h"
#include "utils/ProcessorConfigUtils.h"
@@ -33,8 +36,10 @@ void JoinEnrichmentAttributes::initialize() {
void JoinEnrichmentAttributes::onSchedule(core::ProcessContext& context,
core::ProcessSessionFactory& session_factory) {
using namespace std::literals::chrono_literals;
+ time_out_tracker_.reset();
if (const auto timeout = utils::parseOptionalDurationProperty(context,
TimeoutProperty); timeout && *timeout > 0ms) {
time_out_tracker_.emplace(*timeout);
+ context.setTriggerWhenEmpty(true);
}
max_batch_size_ = utils::parseOptionalU64Property(context, MaxBatchSize);
if (max_batch_size_ && *max_batch_size_ == 0) {
@@ -43,27 +48,23 @@ void
JoinEnrichmentAttributes::onSchedule(core::ProcessContext& context, core::P
ProcessorImpl::onSchedule(context, session_factory);
}
-namespace {
-bool checkRequiredAttributes(const core::FlowFile& flow_file) {
- return flow_file.getAttribute(ENRICHMENT_ROLE).has_value() &&
flow_file.getAttribute(ENRICHMENT_GROUP_ID).has_value();
+void JoinEnrichmentAttributes::addToSessionIfNeeded(const
std::shared_ptr<core::FlowFile>& flow_file, core::ProcessSession& session) {
+ if (session_flow_files_.insert(flow_file->getUUID()).second) {
+ session.add(flow_file);
+ }
}
-} // namespace
void JoinEnrichmentAttributes::join(const std::shared_ptr<core::FlowFile>&
original, const std::shared_ptr<core::FlowFile>& enrichment,
- core::ProcessSession& session) const {
+ core::ProcessSession& session) {
const auto cloned = session.clone(*original);
for (const auto& [k, v] : enrichment->getAttributes()) {
if (k != ENRICHMENT_ROLE) {
cloned->setAttribute(k, v);
}
}
- cloned->setAttribute(ENRICHMENT_ROLE, "JOINED");
- if (!std::ranges::contains(session_flow_files_, original->getUUID())) {
- session.add(original);
- }
- if (!std::ranges::contains(session_flow_files_, enrichment->getUUID())) {
- session.add(enrichment);
- }
+ cloned->setAttribute(ENRICHMENT_ROLE, std::string{JOINED_ROLE});
+ addToSessionIfNeeded(original, session);
+ addToSessionIfNeeded(enrichment, session);
session.transfer(original, Original);
session.transfer(enrichment, Original);
session.transfer(cloned, Joined);
@@ -71,32 +72,32 @@ void JoinEnrichmentAttributes::join(const
std::shared_ptr<core::FlowFile>& origi
void JoinEnrichmentAttributes::handleFlowFile(std::shared_ptr<core::FlowFile>
flow_file, core::ProcessSession& session,
const std::chrono::steady_clock::time_point current_time) {
- if (!checkRequiredAttributes(*flow_file)) {
- logger_->log_warn("{} is missing enrichment.group.id and/or
enrichment.role, routing it to Invalid", flow_file->getId());
+ addToSessionIfNeeded(flow_file, session);
+ const auto role_attribute = flow_file->getAttribute(ENRICHMENT_ROLE);
+ const auto group_id_attribute = flow_file->getAttribute(ENRICHMENT_GROUP_ID);
+ if (!role_attribute || !group_id_attribute) {
+ logger_->log_warn("{} is missing {} and/or {}, routing it to Invalid",
flow_file->getId(), ENRICHMENT_GROUP_ID, ENRICHMENT_ROLE);
session.transfer(flow_file, Invalid);
return;
}
- const auto role = flow_file->getAttribute(ENRICHMENT_ROLE) |
utils::toExpected(make_error_code(core::AttributeErrorCode::MissingAttribute)) |
- utils::andThen(parsing::parseEnum<EnrichmentRole>);
+ const auto role = parsing::parseEnum<EnrichmentRole>(*role_attribute);
if (!role) {
- logger_->log_warn("{} has invalid role due to {}", flow_file->getId(),
role.error());
+ logger_->log_warn("{} has invalid {} due to {}", flow_file->getId(),
ENRICHMENT_ROLE, role.error());
session.transfer(flow_file, Invalid);
return;
}
- std::string group_id = *(flow_file->getAttribute(ENRICHMENT_GROUP_ID));
+ std::string group_id = *group_id_attribute;
auto& my_map = role == EnrichmentRole::ENRICHMENT ? enrichments_ :
originals_;
auto& pair_map = role == EnrichmentRole::ENRICHMENT ? originals_ :
enrichments_;
if (const auto previous_node = my_map.extract(group_id)) {
logger_->log_warn("Encountered duplicate {} for {}, routing both to
Invalid", magic_enum::enum_name(*role), group_id);
+ addToSessionIfNeeded(previous_node.mapped(), session);
session.transfer(flow_file, Invalid);
session.transfer(previous_node.mapped(), Invalid);
- if (!std::ranges::contains(session_flow_files_,
previous_node.mapped()->getUUID())) {
- session.add(previous_node.mapped());
- }
return;
}
@@ -116,6 +117,7 @@ void
JoinEnrichmentAttributes::handleFlowFile(std::shared_ptr<core::FlowFile> fl
}
void JoinEnrichmentAttributes::onTrigger(core::ProcessContext&,
core::ProcessSession& session) {
+ session_flow_files_.clear();
const auto current_time = std::chrono::steady_clock::now();
for (auto flow_file : flow_file_store_.getNewFlowFiles()) {
handleFlowFile(std::move(flow_file), session, current_time);
@@ -133,10 +135,7 @@ void
JoinEnrichmentAttributes::onTrigger(core::ProcessContext&, core::ProcessSes
for (auto timed_out_group :
time_out_tracker_->getTimedOutFlowFiles(current_time)) {
const auto removeFromMap = [&](StoredFlowFileMap& map) {
if (auto timed_out_node = map.extract(timed_out_group)) {
- // Fresh FlowFiles shouldn't time out (we only use a single
time_point per session)
- gsl_AssertAudit(!std::ranges::contains(session_flow_files_,
timed_out_node.mapped()->getUUID()));
-
- session.add(timed_out_node.mapped());
+ addToSessionIfNeeded(timed_out_node.mapped(), session);
session.transfer(timed_out_node.mapped(), TimeoutRelationship);
}
};
@@ -144,8 +143,6 @@ void
JoinEnrichmentAttributes::onTrigger(core::ProcessContext&, core::ProcessSes
removeFromMap(enrichments_);
}
}
-
- session_flow_files_.clear();
}
void JoinEnrichmentAttributes::restore(const std::shared_ptr<core::FlowFile>&
flowFile) {
diff --git
a/extensions/standard-processors/processors/JoinEnrichmentAttributes.h
b/extensions/standard-processors/processors/JoinEnrichmentAttributes.h
index 7a1d18bca..f4f40bebb 100644
--- a/extensions/standard-processors/processors/JoinEnrichmentAttributes.h
+++ b/extensions/standard-processors/processors/JoinEnrichmentAttributes.h
@@ -25,6 +25,7 @@
#include <optional>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -32,8 +33,9 @@
#include "core/ProcessorImpl.h"
#include "core/PropertyDefinitionBuilder.h"
#include "minifi-cpp/core/PropertyDefinition.h"
-#include "utils/Enum.h"
-#include "utils/RegexUtils.h"
+#include "utils/EnrichmentUtils.h"
+#include "utils/Id.h"
+#include "utils/StringUtils.h"
namespace org::apache::nifi::minifi::standard {
@@ -86,7 +88,7 @@ class JoinEnrichmentAttributes : public core::ProcessorImpl {
EXTENSIONAPI static constexpr auto Invalid =
core::RelationshipDefinition{"invalid",
"Any FlowFiles without the requisite attributes will be routed here"};
EXTENSIONAPI static constexpr auto Joined =
core::RelationshipDefinition{"joined",
- "The resultant FlowFile with Records joined together from both the
original and enrichment FlowFiles will be routed to this relationship"};
+ "The resultant FlowFile with attributes joined together from both the
original and enrichment FlowFiles will be routed to this relationship"};
EXTENSIONAPI static constexpr auto Original =
core::RelationshipDefinition{"original",
"Both of the incoming FlowFiles ('original' and 'enrichment') will be
routed to this Relationship. I.e., this is the 'original' version of "
"both of these FlowFiles."};
@@ -127,13 +129,9 @@ class JoinEnrichmentAttributes : public
core::ProcessorImpl {
void restore(const std::shared_ptr<core::FlowFile>& flowFile) override;
private:
- enum class EnrichmentRole {
- ORIGINAL,
- ENRICHMENT,
- };
-
void handleFlowFile(std::shared_ptr<core::FlowFile> flow_file,
core::ProcessSession& session, std::chrono::steady_clock::time_point
current_time);
- void join(const std::shared_ptr<core::FlowFile>& original, const
std::shared_ptr<core::FlowFile>& enrichment, core::ProcessSession& session)
const;
+ void join(const std::shared_ptr<core::FlowFile>& original, const
std::shared_ptr<core::FlowFile>& enrichment, core::ProcessSession& session);
+ void addToSessionIfNeeded(const std::shared_ptr<core::FlowFile>& flow_file,
core::ProcessSession& session);
core::FlowFileStore flow_file_store_;
// We need to track current session's FlowFiles (we cant add those)
diff --git a/extensions/standard-processors/tests/features/enrichment.feature
b/extensions/standard-processors/tests/features/enrichment.feature
index 429b535a8..e45fa2d9c 100644
--- a/extensions/standard-processors/tests/features/enrichment.feature
+++ b/extensions/standard-processors/tests/features/enrichment.feature
@@ -22,6 +22,9 @@ Feature: ForkEnrichment and JoinEnrichmentAttributes
And the "Data Format" property of the GenerateFlowFile processor is set to
"Text"
And the "Unique FlowFiles" property of the GenerateFlowFile processor is
set to "false"
+ And an UpdateAttribute processor with the name "SetOriginalAttributes" and
the "kept_prop" property set to "original_value"
+ And the "overwritten_prop" property of the SetOriginalAttributes processor
is set to "original_value"
+
And a ForkEnrichment processor
And a JoinEnrichmentAttributes processor
@@ -29,19 +32,23 @@ Feature: ForkEnrichment and JoinEnrichmentAttributes
And the "Replacement Strategy" property of the ReplaceText processor is
set to "Always Replace"
And the "Replacement Value" property of the ReplaceText processor is set
to "replaced_content"
- And an UpdateAttribute processor with the "extra_prop" property set to
"foo"
+ And an UpdateAttribute processor with the name "EnrichAttributes" and the
"extra_prop" property set to "foo"
+ And the "overwritten_prop" property of the EnrichAttributes processor is
set to "enriched_value"
And a LogAttribute processor with the "Log Payload" property set to "true"
- And the "success" relationship of the GenerateFlowFile processor is
connected to the ForkEnrichment
+ And the "success" relationship of the GenerateFlowFile processor is
connected to the SetOriginalAttributes
+ And the "success" relationship of the SetOriginalAttributes processor is
connected to the ForkEnrichment
And the "original" relationship of the ForkEnrichment processor is
connected to the JoinEnrichmentAttributes
And the "enrichment" relationship of the ForkEnrichment processor is
connected to the ReplaceText
- And the "success" relationship of the ReplaceText processor is connected
to the UpdateAttribute
- And the "success" relationship of the UpdateAttribute processor is
connected to the JoinEnrichmentAttributes
+ And the "success" relationship of the ReplaceText processor is connected
to the EnrichAttributes
+ And the "success" relationship of the EnrichAttributes processor is
connected to the JoinEnrichmentAttributes
And the "joined" relationship of the JoinEnrichmentAttributes processor is
connected to the LogAttribute
And JoinEnrichmentAttributes's original relationship is auto-terminated
And LogAttribute's success relationship is auto-terminated
When the MiNiFi instance starts up
Then the Minifi logs contain the following message: "key:enrichment.role
value:JOINED" in less than 10 seconds
And the Minifi logs contain the following message: "key:extra_prop
value:foo" in less than 1 second
+ And the Minifi logs contain the following message: "key:kept_prop
value:original_value" in less than 1 second
+ And the Minifi logs contain the following message: "key:overwritten_prop
value:enriched_value" in less than 1 second
And the Minifi logs contain the following message: "original_content" in
less than 1 second
diff --git a/extensions/standard-processors/tests/unit/ForkEnrichmentTests.cpp
b/extensions/standard-processors/tests/unit/ForkEnrichmentTests.cpp
index a81ae1408..a1dd69231 100644
--- a/extensions/standard-processors/tests/unit/ForkEnrichmentTests.cpp
+++ b/extensions/standard-processors/tests/unit/ForkEnrichmentTests.cpp
@@ -37,11 +37,11 @@ TEST_CASE("Fork Enrichment processor works") {
CHECK(original_content == enrichment_content);
CHECK(original_content == "test_content");
-
CHECK(original_results.at(0)->getAttribute(ForkEnrichment::EnrichmentRole.name)
== "ORIGINAL");
-
CHECK(enrichment_results.at(0)->getAttribute(ForkEnrichment::EnrichmentRole.name)
== "ENRICHMENT");
+
CHECK(original_results.at(0)->getAttribute(ForkEnrichment::EnrichmentRoleOutputAttribute.name)
== "ORIGINAL");
+
CHECK(enrichment_results.at(0)->getAttribute(ForkEnrichment::EnrichmentRoleOutputAttribute.name)
== "ENRICHMENT");
-
CHECK(original_results.at(0)->getAttribute(ForkEnrichment::EnrichmentGroupId.name)
==
-
enrichment_results.at(0)->getAttribute(ForkEnrichment::EnrichmentGroupId.name));
+
CHECK(original_results.at(0)->getAttribute(ForkEnrichment::EnrichmentGroupIdOutputAttribute.name)
==
+
enrichment_results.at(0)->getAttribute(ForkEnrichment::EnrichmentGroupIdOutputAttribute.name));
}
TEST_CASE("ForkEnrichment no max batch size") {
diff --git
a/extensions/standard-processors/tests/unit/JoinEnrichmentAttributesTests.cpp
b/extensions/standard-processors/tests/unit/JoinEnrichmentAttributesTests.cpp
index beeb2db8e..7e9c31d06 100644
---
a/extensions/standard-processors/tests/unit/JoinEnrichmentAttributesTests.cpp
+++
b/extensions/standard-processors/tests/unit/JoinEnrichmentAttributesTests.cpp
@@ -18,6 +18,7 @@
#include <thread>
+#include "FlowFileRecord.h"
#include "JoinEnrichmentAttributes.h"
#include "unit/Catch.h"
#include "unit/ProcessorUtils.h"
@@ -112,7 +113,7 @@ TEST_CASE("JoinEnrichmentAttributes test timeout") {
// First trigger no output (it holds the original waiting for its pair)
CHECK(std::ranges::all_of(first_trigger, [](const auto& res) -> bool {
return res.second.empty(); }));
- std::this_thread::sleep_for(1ms);
+ std::this_thread::sleep_for(10ms);
const auto second_trigger =
test_controller.trigger(minifi::test::InputFlowFileData{.content = "second",
.attributes = {{std::string{ENRICHMENT_GROUP_ID}, "bar"},
{std::string{ENRICHMENT_ROLE}, "ENRICHMENT"}, {"second_attr", "2"}}});
CHECK(second_trigger.at(JoinEnrichmentAttributes::Original).empty());
@@ -121,6 +122,54 @@ TEST_CASE("JoinEnrichmentAttributes test timeout") {
REQUIRE(second_trigger.at(JoinEnrichmentAttributes::Joined).empty());
}
+TEST_CASE("JoinEnrichmentAttributes times out without further input") {
+ minifi::test::SingleProcessorTestController
+
test_controller(minifi::test::utils::make_processor<JoinEnrichmentAttributes>("JoinEnrichmentAttributes"));
+ const auto proc = test_controller.getProcessor();
+ CHECK(test_controller.plan->setProperty(proc,
JoinEnrichmentAttributes::TimeoutProperty.name, "1 ms"));
+
+ const auto first_trigger =
test_controller.trigger(minifi::test::InputFlowFileData{.content = "first",
+ .attributes = {{std::string{ENRICHMENT_GROUP_ID}, "foo"},
{std::string{ENRICHMENT_ROLE}, "ORIGINAL"}}});
+ CHECK(std::ranges::all_of(first_trigger, [](const auto& res) -> bool {
return res.second.empty(); }));
+ // the scheduler only triggers us again without input if the processor asked
for it
+ CHECK(proc->getTriggerWhenEmpty());
+
+ std::this_thread::sleep_for(10ms);
+ const auto second_trigger = test_controller.trigger();
+
CHECK(second_trigger.at(JoinEnrichmentAttributes::TimeoutRelationship).size()
== 1);
+ CHECK(second_trigger.at(JoinEnrichmentAttributes::Joined).empty());
+}
+
+TEST_CASE("JoinEnrichmentAttributes restored FlowFile without the enrichment
attributes is routed to Invalid") {
+ minifi::test::SingleProcessorTestController
+
test_controller(minifi::test::utils::make_processor<JoinEnrichmentAttributes>("JoinEnrichmentAttributes"));
+
test_controller.getProcessor()->restore(std::make_shared<minifi::FlowFileRecordImpl>());
+
+ const auto trigger_result = test_controller.trigger();
+ CHECK(trigger_result.at(JoinEnrichmentAttributes::Invalid).size() == 1);
+}
+
+TEST_CASE("JoinEnrichmentAttributes joins a restored FlowFile with its pair") {
+ minifi::test::SingleProcessorTestController
+
test_controller(minifi::test::utils::make_processor<JoinEnrichmentAttributes>("JoinEnrichmentAttributes"));
+ const auto restored_original =
std::make_shared<minifi::FlowFileRecordImpl>();
+ restored_original->setAttribute(ENRICHMENT_GROUP_ID, "foo");
+ restored_original->setAttribute(ENRICHMENT_ROLE, "ORIGINAL");
+ restored_original->setAttribute("first_attr", "1");
+ test_controller.getProcessor()->restore(restored_original);
+
+ const auto trigger_result =
test_controller.trigger(minifi::test::InputFlowFileData{.content = "second",
+ .attributes = {{std::string{ENRICHMENT_GROUP_ID}, "foo"},
{std::string{ENRICHMENT_ROLE}, "ENRICHMENT"}, {"second_attr", "2"}}});
+ CHECK(trigger_result.at(JoinEnrichmentAttributes::Invalid).empty());
+ CHECK(trigger_result.at(JoinEnrichmentAttributes::Original).size() == 2);
+ REQUIRE(trigger_result.at(JoinEnrichmentAttributes::Joined).size() == 1);
+
+ const auto joined_attrs =
trigger_result.at(JoinEnrichmentAttributes::Joined).at(0)->getAttributes();
+ CHECK(joined_attrs.at(std::string{ENRICHMENT_ROLE}) == "JOINED");
+ CHECK(joined_attrs.at("first_attr") == "1");
+ CHECK(joined_attrs.at("second_attr") == "2");
+}
+
TEST_CASE("JoinEnrichmentAttributes no max batch size") {
minifi::test::SingleProcessorTestController
test_controller(minifi::test::utils::make_processor<JoinEnrichmentAttributes>("JoinEnrichmentAttributes"));
diff --git a/extensions/standard-processors/utils/EnrichmentUtils.h
b/extensions/standard-processors/utils/EnrichmentUtils.h
index 76dae0052..e56c59b59 100644
--- a/extensions/standard-processors/utils/EnrichmentUtils.h
+++ b/extensions/standard-processors/utils/EnrichmentUtils.h
@@ -19,5 +19,16 @@
#include <string_view>
+namespace org::apache::nifi::minifi::standard {
+
constexpr std::string_view ENRICHMENT_ROLE = "enrichment.role";
constexpr std::string_view ENRICHMENT_GROUP_ID = "enrichment.group.id";
+
+enum class EnrichmentRole {
+ ORIGINAL,
+ ENRICHMENT,
+};
+
+constexpr std::string_view JOINED_ROLE = "JOINED";
+
+} // namespace org::apache::nifi::minifi::standard