This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new dac22e67e4 issue #3165: add Workflow from field to Workflow Executor
(#7809)
dac22e67e4 is described below
commit dac22e67e419f589e61e479823f0cee3f68867fd
Author: Matt Casters <[email protected]>
AuthorDate: Thu Aug 6 20:39:06 2026 +0200
issue #3165: add Workflow from field to Workflow Executor (#7809)
Bring feature parity with Pipeline Executor so the child workflow path can
come from an input stream field. Load/reload at runtime without mutating
shared transform meta, add dialog controls, unit tests, and extend the
main-0083 integration suite.
---
.../pipeline/transforms/workflow-executor.adoc | 7 +-
...> 0083-workflow-executor-fromfield-called1.hwf} | 67 ++----
...> 0083-workflow-executor-fromfield-called2.hwf} | 67 ++----
.../0083-workflow-executor-fromfield.hpl | 256 +++++++++++++++++++++
.../main-0083-workflow-executor-test.hwf | 44 +++-
.../workflowexecutor/WorkflowExecutor.java | 228 ++++++++++--------
.../workflowexecutor/WorkflowExecutorData.java | 9 +
.../workflowexecutor/WorkflowExecutorDialog.java | 143 ++++++++++--
.../workflowexecutor/WorkflowExecutorMeta.java | 40 +++-
.../messages/messages_en_US.properties | 3 +
.../workflowexecutor/WorkflowExecutorMetaTest.java | 23 ++
.../workflowexecutor/WorkflowExecutorTest.java | 151 ++++++++++++
.../transforms/workflowexecutor/minimal-child.hwf | 65 +-----
...l => workflow-executor-fromfield-transform.xml} | 2 +
.../test/resources/workflow-executor-transform.xml | 2 +
15 files changed, 839 insertions(+), 268 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
index 33b1c7065c..2ca1dccfd6 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
@@ -69,7 +69,12 @@ Samples (samples project)
|===
|Option|Description
|Transform name|Name of the transform.
-|Workflow|Use this option to specify a workflow stored in a file (.hwf file)
+|Workflow|Use this option to specify a workflow stored in a file (.hwf file).
+The filename may contain variables (for example, you can use the built-in
Internal.Pipeline.Filename.Directory variable to construct a filename relative
to the current pipeline), or you can use the "Browse" button to select a file
using a file browser.
+|Workflow from field?|Enable to specify the workflow file name(s) in a field
in the input stream.
+|Workflow field|When the previous option is enabled, you can specify the field
that will contain the workflow filename(s) at runtime. The filename may contain
variables.
+
+*NOTE:* It must be considered that, by specifying the workflow file name using
this option, we can experience a little performance penalty because the
workflow that will be executed will be initialized at runtime and not during
the initialization phase as usual. On the other side this option gives greater
flexibility in specifying the workflow filename dynamically and this could be
useful for many use-cases.
|Run configuration|Specify the Workflow Run Configuration to be used for
execution.
|===
diff --git a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
b/integration-tests/transforms/0083-workflow-executor-fromfield-called1.hwf
similarity index 58%
copy from integration-tests/transforms/main-0083-workflow-executor-test.hwf
copy to
integration-tests/transforms/0083-workflow-executor-fromfield-called1.hwf
index 895b54c5da..c5dc69b72c 100644
--- a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
+++ b/integration-tests/transforms/0083-workflow-executor-fromfield-called1.hwf
@@ -18,15 +18,15 @@ limitations under the License.
-->
<workflow>
- <name>main-0083-workflow-executor-test</name>
+ <name>0083-workflow-executor-fromfield-called1</name>
<name_sync_with_filename>Y</name_sync_with_filename>
<description/>
<extended_description/>
<workflow_version/>
<created_user>-</created_user>
- <created_date>2020/12/07 13:03:35.562</created_date>
+ <created_date>2026/03/23 12:00:00.000</created_date>
<modified_user>-</modified_user>
- <modified_date>2020/12/07 13:03:35.562</modified_date>
+ <modified_date>2026/03/23 12:00:00.000</modified_date>
<parameters>
</parameters>
<actions>
@@ -45,80 +45,49 @@ limitations under the License.
<schedulerType>0</schedulerType>
<weekDay>1</weekDay>
<parallel>N</parallel>
- <xloc>160</xloc>
- <yloc>96</yloc>
+ <xloc>112</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
<action>
- <name>Success</name>
- <description/>
- <type>SUCCESS</type>
- <attributes/>
- <parallel>N</parallel>
- <xloc>544</xloc>
- <yloc>96</yloc>
- <attributes_hac/>
- </action>
- <action>
- <name>Abort workflow</name>
+ <name>Write to log</name>
<description/>
- <type>ABORT</type>
+ <type>WRITE_TO_LOG</type>
<attributes/>
- <always_log_rows>N</always_log_rows>
+ <loglevel>Basic</loglevel>
+ <logmessage>fromfield called1</logmessage>
+ <logsubject/>
<parallel>N</parallel>
- <xloc>320</xloc>
- <yloc>256</yloc>
+ <xloc>304</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
<action>
- <name>0083-workflow-executor-run-wf.hpl</name>
+ <name>Success</name>
<description/>
- <type>PIPELINE</type>
+ <type>SUCCESS</type>
<attributes/>
- <add_date>N</add_date>
- <add_time>N</add_time>
- <clear_files>N</clear_files>
- <clear_rows>N</clear_rows>
- <create_parent_folder>N</create_parent_folder>
- <exec_per_row>N</exec_per_row>
- <filename>${PROJECT_HOME}/0083-workflow-executor-run-wf.hpl</filename>
- <loglevel>Basic</loglevel>
- <parameters>
- <pass_all_parameters>Y</pass_all_parameters>
- </parameters>
- <params_from_previous>N</params_from_previous>
- <run_configuration>local</run_configuration>
- <set_append_logfile>N</set_append_logfile>
- <set_logfile>N</set_logfile>
- <wait_until_finished>Y</wait_until_finished>
<parallel>N</parallel>
- <xloc>320</xloc>
- <yloc>98</yloc>
+ <xloc>496</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
</actions>
<hops>
<hop>
<from>START</from>
- <to>0083-workflow-executor-run-wf.hpl</to>
+ <to>Write to log</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>Y</unconditional>
</hop>
<hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
+ <from>Write to log</from>
<to>Success</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>N</unconditional>
</hop>
- <hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
- <to>Abort workflow</to>
- <enabled>Y</enabled>
- <evaluation>N</evaluation>
- <unconditional>N</unconditional>
- </hop>
</hops>
<notepads>
</notepads>
diff --git a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
b/integration-tests/transforms/0083-workflow-executor-fromfield-called2.hwf
similarity index 58%
copy from integration-tests/transforms/main-0083-workflow-executor-test.hwf
copy to
integration-tests/transforms/0083-workflow-executor-fromfield-called2.hwf
index 895b54c5da..c948e52581 100644
--- a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
+++ b/integration-tests/transforms/0083-workflow-executor-fromfield-called2.hwf
@@ -18,15 +18,15 @@ limitations under the License.
-->
<workflow>
- <name>main-0083-workflow-executor-test</name>
+ <name>0083-workflow-executor-fromfield-called2</name>
<name_sync_with_filename>Y</name_sync_with_filename>
<description/>
<extended_description/>
<workflow_version/>
<created_user>-</created_user>
- <created_date>2020/12/07 13:03:35.562</created_date>
+ <created_date>2026/03/23 12:00:00.000</created_date>
<modified_user>-</modified_user>
- <modified_date>2020/12/07 13:03:35.562</modified_date>
+ <modified_date>2026/03/23 12:00:00.000</modified_date>
<parameters>
</parameters>
<actions>
@@ -45,80 +45,49 @@ limitations under the License.
<schedulerType>0</schedulerType>
<weekDay>1</weekDay>
<parallel>N</parallel>
- <xloc>160</xloc>
- <yloc>96</yloc>
+ <xloc>112</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
<action>
- <name>Success</name>
- <description/>
- <type>SUCCESS</type>
- <attributes/>
- <parallel>N</parallel>
- <xloc>544</xloc>
- <yloc>96</yloc>
- <attributes_hac/>
- </action>
- <action>
- <name>Abort workflow</name>
+ <name>Write to log</name>
<description/>
- <type>ABORT</type>
+ <type>WRITE_TO_LOG</type>
<attributes/>
- <always_log_rows>N</always_log_rows>
+ <loglevel>Basic</loglevel>
+ <logmessage>fromfield called2</logmessage>
+ <logsubject/>
<parallel>N</parallel>
- <xloc>320</xloc>
- <yloc>256</yloc>
+ <xloc>304</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
<action>
- <name>0083-workflow-executor-run-wf.hpl</name>
+ <name>Success</name>
<description/>
- <type>PIPELINE</type>
+ <type>SUCCESS</type>
<attributes/>
- <add_date>N</add_date>
- <add_time>N</add_time>
- <clear_files>N</clear_files>
- <clear_rows>N</clear_rows>
- <create_parent_folder>N</create_parent_folder>
- <exec_per_row>N</exec_per_row>
- <filename>${PROJECT_HOME}/0083-workflow-executor-run-wf.hpl</filename>
- <loglevel>Basic</loglevel>
- <parameters>
- <pass_all_parameters>Y</pass_all_parameters>
- </parameters>
- <params_from_previous>N</params_from_previous>
- <run_configuration>local</run_configuration>
- <set_append_logfile>N</set_append_logfile>
- <set_logfile>N</set_logfile>
- <wait_until_finished>Y</wait_until_finished>
<parallel>N</parallel>
- <xloc>320</xloc>
- <yloc>98</yloc>
+ <xloc>496</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
</actions>
<hops>
<hop>
<from>START</from>
- <to>0083-workflow-executor-run-wf.hpl</to>
+ <to>Write to log</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>Y</unconditional>
</hop>
<hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
+ <from>Write to log</from>
<to>Success</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>N</unconditional>
</hop>
- <hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
- <to>Abort workflow</to>
- <enabled>Y</enabled>
- <evaluation>N</evaluation>
- <unconditional>N</unconditional>
- </hop>
</hops>
<notepads>
</notepads>
diff --git a/integration-tests/transforms/0083-workflow-executor-fromfield.hpl
b/integration-tests/transforms/0083-workflow-executor-fromfield.hpl
new file mode 100644
index 0000000000..666c77ae78
--- /dev/null
+++ b/integration-tests/transforms/0083-workflow-executor-fromfield.hpl
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+ <info>
+ <name>0083-workflow-executor-fromfield</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/03/23 12:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/03/23 12:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Workflow executor</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Workflow executor</from>
+ <to>Group by</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Group by</from>
+ <to>count = 2</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>count = 2</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>count = 2</from>
+ <to>Success</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <always_log_rows>Y</always_log_rows>
+ <message>Workflow executor from field: expected 2 successful
executions</message>
+ <row_threshold>0</row_threshold>
+ <attributes/>
+ <GUI>
+ <xloc>704</xloc>
+ <yloc>224</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Data grid</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <set_empty_string>N</set_empty_string>
+ <length>-1</length>
+ <name>wf_to_run</name>
+ <precision>-1</precision>
+ <type>String</type>
+ </field>
+ </fields>
+ <data>
+ <line>
+
<item>${PROJECT_HOME}/0083-workflow-executor-fromfield-called1.hwf</item>
+ </line>
+ <line>
+
<item>${PROJECT_HOME}/0083-workflow-executor-fromfield-called2.hwf</item>
+ </line>
+ </data>
+ <attributes/>
+ <GUI>
+ <xloc>112</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Group by</name>
+ <type>GroupBy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <all_rows>N</all_rows>
+ <ignore_aggregate>N</ignore_aggregate>
+ <field_ignore/>
+ <directory>${java.io.tmpdir}</directory>
+ <prefix>grp</prefix>
+ <add_linenr>N</add_linenr>
+ <linenr_fieldname/>
+ <give_back_row>N</give_back_row>
+ <group>
+ </group>
+ <fields>
+ <field>
+ <aggregate>count</aggregate>
+ <subject>ExecutionResult</subject>
+ <type>COUNT_ALL</type>
+ <valuefield/>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Success</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>832</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Workflow executor</name>
+ <type>WorkflowExecutor</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <run_configuration>local</run_configuration>
+ <filename/>
+ <filenameInField>Y</filenameInField>
+ <filenameField>wf_to_run</filenameField>
+ <group_size>1</group_size>
+ <group_field/>
+ <group_time/>
+ <parameters>
+ </parameters>
+ <inherit_all_vars>Y</inherit_all_vars>
+ <execution_result_target_transform>Group
by</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+ <result_files_file_name_field>FileName</result_files_file_name_field>
+ <attributes/>
+ <GUI>
+ <xloc>272</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>count = 2</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>Success</send_true_to>
+ <send_false_to>Abort</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>count</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>2</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>592</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
b/integration-tests/transforms/main-0083-workflow-executor-test.hwf
index 895b54c5da..941586f1dc 100644
--- a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
+++ b/integration-tests/transforms/main-0083-workflow-executor-test.hwf
@@ -55,7 +55,7 @@ limitations under the License.
<type>SUCCESS</type>
<attributes/>
<parallel>N</parallel>
- <xloc>544</xloc>
+ <xloc>768</xloc>
<yloc>96</yloc>
<attributes_hac/>
</action>
@@ -96,6 +96,32 @@ limitations under the License.
<yloc>98</yloc>
<attributes_hac/>
</action>
+ <action>
+ <name>0083-workflow-executor-fromfield.hpl</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <clear_files>N</clear_files>
+ <clear_rows>N</clear_rows>
+ <create_parent_folder>N</create_parent_folder>
+ <exec_per_row>N</exec_per_row>
+ <filename>${PROJECT_HOME}/0083-workflow-executor-fromfield.hpl</filename>
+ <loglevel>Basic</loglevel>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <params_from_previous>N</params_from_previous>
+ <run_configuration>local</run_configuration>
+ <set_append_logfile>N</set_append_logfile>
+ <set_logfile>N</set_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <parallel>N</parallel>
+ <xloc>544</xloc>
+ <yloc>96</yloc>
+ <attributes_hac/>
+ </action>
</actions>
<hops>
<hop>
@@ -107,7 +133,7 @@ limitations under the License.
</hop>
<hop>
<from>0083-workflow-executor-run-wf.hpl</from>
- <to>Success</to>
+ <to>0083-workflow-executor-fromfield.hpl</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>N</unconditional>
@@ -119,6 +145,20 @@ limitations under the License.
<evaluation>N</evaluation>
<unconditional>N</unconditional>
</hop>
+ <hop>
+ <from>0083-workflow-executor-fromfield.hpl</from>
+ <to>Success</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>0083-workflow-executor-fromfield.hpl</from>
+ <to>Abort workflow</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
</hops>
<notepads>
</notepads>
diff --git
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
index 109e8d91fe..956115ba7f 100644
---
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
+++
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
@@ -71,7 +71,7 @@ public class WorkflowExecutor extends
BaseTransform<WorkflowExecutorMeta, Workfl
Object[] row = getRow();
if (row == null) {
- if (!data.groupBuffer.isEmpty()) {
+ if (data.groupBuffer != null && !data.groupBuffer.isEmpty()) {
executeWorkflow();
}
setOutputDone();
@@ -80,69 +80,40 @@ public class WorkflowExecutor extends
BaseTransform<WorkflowExecutorMeta, Workfl
if (first) {
first = false;
-
- // calculate the various output row layouts first...
- //
- data.inputRowMeta = getInputRowMeta();
- data.executionResultsOutputRowMeta = data.inputRowMeta.clone();
- data.resultRowsOutputRowMeta = data.inputRowMeta.clone();
- data.resultFilesOutputRowMeta = data.inputRowMeta.clone();
-
- if (meta.getExecutionResultTargetTransformMeta() != null) {
- meta.getFields(
- data.executionResultsOutputRowMeta,
- getTransformName(),
- null,
- meta.getExecutionResultTargetTransformMeta(),
- this,
- metadataProvider);
- data.executionResultRowSet =
-
findOutputRowSet(meta.getExecutionResultTargetTransformMeta().getName());
- }
- if (meta.getResultRowsTargetTransformMeta() != null) {
- meta.getFields(
- data.resultRowsOutputRowMeta,
- getTransformName(),
- null,
- meta.getResultRowsTargetTransformMeta(),
- this,
- metadataProvider);
- data.resultRowsRowSet =
-
findOutputRowSet(meta.getResultRowsTargetTransformMeta().getName());
- }
- if (meta.getResultFilesTargetTransformMeta() != null) {
- meta.getFields(
- data.resultFilesOutputRowMeta,
- getTransformName(),
- null,
- meta.getResultFilesTargetTransformMeta(),
- this,
- metadataProvider);
- data.resultFilesRowSet =
-
findOutputRowSet(meta.getResultFilesTargetTransformMeta().getName());
+ if (!meta.isFilenameInField()) {
+ initOnFirstProcessingIteration();
}
+ }
- // Remember which column to group on, if any...
- //
- data.groupFieldIndex = -1;
- if (!Utils.isEmpty(data.groupField)) {
- data.groupFieldIndex =
getInputRowMeta().indexOfValue(data.groupField);
- if (data.groupFieldIndex < 0) {
+ if (meta.isFilenameInField()) {
+ int pos = getInputRowMeta().indexOfValue(meta.getFilenameField());
+ if (pos < 0) {
+ throw new HopException(
+ BaseMessages.getString(
+ PKG, "WorkflowExecutor.Exception.UnableToFindField",
meta.getFilenameField()));
+ }
+ String filename = getInputRowMeta().getString(row, pos);
+ if (data.prevFilename == null || !data.prevFilename.equals(filename)) {
+ // When grouping by size, flush a partial group before switching
child workflow so each
+ // execution only receives rows for the previous path. Per-copy
buffer; shared meta is
+ // not touched (see runtimeWorkflowFilename).
+ if (data.prevFilename != null
+ && data.groupSize > 0
+ && data.groupBuffer != null
+ && !data.groupBuffer.isEmpty()) {
+ executeWorkflow();
+ }
+ if (isDetailed()) {
+ logDetailed("Identified a new workflow to execute: '" + filename +
"'");
+ }
+ data.runtimeWorkflowFilename = filename;
+ data.prevFilename = filename;
+ if (!initWorkflow()) {
throw new HopException(
BaseMessages.getString(
- PKG, "WorkflowExecutor.Exception.GroupFieldNotFound",
data.groupField));
+ PKG, "WorkflowExecutor.Exception.UnableToLoadWorkflow",
filename));
}
- data.groupFieldMeta =
getInputRowMeta().getValueMeta(data.groupFieldIndex);
- }
-
- // Edge case: no grouping information given at all...
- //
- if (data.groupSize < 0 && data.groupFieldIndex < 0 && data.groupTime
<= 0) {
- // We assume that we want to execute once per input row, not once
for all the input
- // rows...
- // This is the default but the case might come about anyway
- //
- data.groupSize = 1;
+ initOnFirstProcessingIteration();
}
}
@@ -181,6 +152,70 @@ public class WorkflowExecutor extends
BaseTransform<WorkflowExecutorMeta, Workfl
}
}
+ private void initOnFirstProcessingIteration() throws HopException {
+ // calculate the various output row layouts first...
+ //
+ data.inputRowMeta = getInputRowMeta();
+ data.executionResultsOutputRowMeta = data.inputRowMeta.clone();
+ data.resultRowsOutputRowMeta = data.inputRowMeta.clone();
+ data.resultFilesOutputRowMeta = data.inputRowMeta.clone();
+
+ if (meta.getExecutionResultTargetTransformMeta() != null) {
+ meta.getFields(
+ data.executionResultsOutputRowMeta,
+ getTransformName(),
+ null,
+ meta.getExecutionResultTargetTransformMeta(),
+ this,
+ metadataProvider);
+ data.executionResultRowSet =
+
findOutputRowSet(meta.getExecutionResultTargetTransformMeta().getName());
+ }
+ if (meta.getResultRowsTargetTransformMeta() != null) {
+ meta.getFields(
+ data.resultRowsOutputRowMeta,
+ getTransformName(),
+ null,
+ meta.getResultRowsTargetTransformMeta(),
+ this,
+ metadataProvider);
+ data.resultRowsRowSet =
findOutputRowSet(meta.getResultRowsTargetTransformMeta().getName());
+ }
+ if (meta.getResultFilesTargetTransformMeta() != null) {
+ meta.getFields(
+ data.resultFilesOutputRowMeta,
+ getTransformName(),
+ null,
+ meta.getResultFilesTargetTransformMeta(),
+ this,
+ metadataProvider);
+ data.resultFilesRowSet =
findOutputRowSet(meta.getResultFilesTargetTransformMeta().getName());
+ }
+
+ // Remember which column to group on, if any...
+ //
+ data.groupFieldIndex = -1;
+ if (!Utils.isEmpty(data.groupField)) {
+ data.groupFieldIndex = getInputRowMeta().indexOfValue(data.groupField);
+ if (data.groupFieldIndex < 0) {
+ throw new HopException(
+ BaseMessages.getString(
+ PKG, "WorkflowExecutor.Exception.GroupFieldNotFound",
data.groupField));
+ }
+ data.groupFieldMeta =
getInputRowMeta().getValueMeta(data.groupFieldIndex);
+ }
+
+ // Edge case: no grouping information given at all...
+ //
+ if (data.groupSize < 0 && data.groupFieldIndex < 0 && data.groupTime <= 0)
{
+ // We assume that we want to execute once per input row, not once for
all the input
+ // rows...
+ // This is the default but the case might come about anyway
+ //
+ data.groupSize = 1;
+ }
+ }
+
private void executeWorkflow() throws HopException {
// If we got 0 rows on input we don't really want to execute the workflow
@@ -427,45 +462,42 @@ public class WorkflowExecutor extends
BaseTransform<WorkflowExecutorMeta, Workfl
public boolean init() {
if (super.init()) {
- // First we need to load the mapping (pipeline)
try {
+ // Init commons parameters and data structures. groupBuffer is
initialized only once.
+ data.groupBuffer = new ArrayList<>();
- data.executorWorkflowMeta =
- WorkflowExecutorMeta.loadWorkflowMeta(meta, metadataProvider,
this);
-
- // Do we have a workflow at all?
- //
- if (data.executorWorkflowMeta != null) {
- data.groupBuffer = new ArrayList<>();
-
- // How many rows do we group together for the workflow?
- //
- data.groupSize = -1;
- if (!Utils.isEmpty(meta.getGroupSize())) {
- data.groupSize = Const.toIntExpanded(resolve(meta.getGroupSize()),
-1);
- }
+ // How many rows do we group together for the workflow?
+ data.groupSize = -1;
+ if (!Utils.isEmpty(meta.getGroupSize())) {
+ data.groupSize = Const.toIntExpanded(resolve(meta.getGroupSize()),
-1);
+ }
- // Is there a grouping time set?
- //
- data.groupTime = -1;
- if (!Utils.isEmpty(meta.getGroupTime())) {
- data.groupTime = Const.toInt(resolve(meta.getGroupTime()), -1);
- }
- data.groupTimeStart = System.currentTimeMillis();
+ // Is there a grouping time set?
+ data.groupTime = -1;
+ if (!Utils.isEmpty(meta.getGroupTime())) {
+ data.groupTime = Const.toInt(resolve(meta.getGroupTime()), -1);
+ }
+ data.groupTimeStart = System.currentTimeMillis();
- // Is there a grouping field set?
- //
- data.groupField = null;
- if (!Utils.isEmpty(meta.getGroupField())) {
- data.groupField = resolve(meta.getGroupField());
- }
+ // Is there a grouping field set?
+ data.groupField = null;
+ if (!Utils.isEmpty(meta.getGroupField())) {
+ data.groupField = resolve(meta.getGroupField());
+ }
- // That's all for now...
- return true;
- } else {
- logError("No valid workflow was specified nor loaded!");
+ // First we need to load the workflow (unless the filename comes from
a field)
+ if ((!meta.isFilenameInField() && Utils.isEmpty(meta.getFilename()))
+ || (meta.isFilenameInField() &&
Utils.isEmpty(meta.getFilenameField()))) {
+ logError("No workflow filename given either in path or in a field!");
return false;
}
+
+ if (!meta.isFilenameInField() && !Utils.isEmpty(meta.getFilename())) {
+ return initWorkflow();
+ }
+
+ // Filename comes from a field; load on first row in processRow()
+ return true;
} catch (Exception e) {
logError("Unable to load the executor workflow because of an error :
", e);
}
@@ -473,6 +505,20 @@ public class WorkflowExecutor extends
BaseTransform<WorkflowExecutorMeta, Workfl
return false;
}
+ private boolean initWorkflow() throws HopException {
+ String explicit = meta.isFilenameInField() ? data.runtimeWorkflowFilename
: null;
+ data.executorWorkflowMeta =
+ WorkflowExecutorMeta.loadWorkflowMeta(meta, explicit,
metadataProvider, this);
+
+ if (data.executorWorkflowMeta != null) {
+ data.groupTimeStart = System.currentTimeMillis();
+ return true;
+ } else {
+ logError("No valid workflow was specified nor loaded!");
+ return false;
+ }
+ }
+
@Override
public void dispose() {
data.groupBuffer = null;
diff --git
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorData.java
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorData.java
index 9ef5a36978..f5173e7f66 100644
---
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorData.java
+++
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorData.java
@@ -48,6 +48,15 @@ public class WorkflowExecutorData extends BaseTransformData
implements ITransfor
public IRowSet resultFilesRowSet;
public IRowSet executionResultRowSet;
+ public String prevFilename;
+
+ /**
+ * When the child workflow path comes from a field, the resolved path for
this transform copy
+ * only. Shared transform meta filename must not be updated at runtime so
multiple copies stay
+ * isolated.
+ */
+ public String runtimeWorkflowFilename;
+
public WorkflowExecutorData() {
super();
}
diff --git
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
index b6cb1402e9..82f27ceceb 100644
---
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
+++
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
@@ -54,6 +54,12 @@ import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
@@ -77,7 +83,16 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
private WorkflowExecutorMeta workflowExecutorMeta;
+ private Label wlPath;
private TextVar wPath;
+ private Button wbBrowse;
+
+ private Button wbWorkflowNameInField;
+
+ private Label wlWorkflowNameField;
+ private ComboVar wWorkflowNameField;
+
+ private boolean gotPreviousFields = false;
protected Label wlRunConfiguration;
protected ComboVar wRunConfiguration;
@@ -144,7 +159,9 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
buildButtonBar().ok(e -> ok()).cancel(e -> cancel()).build();
- Label wlPath = new Label(shell, SWT.RIGHT);
+ ModifyListener lsMod = e -> workflowExecutorMeta.setChanged();
+
+ wlPath = new Label(shell, SWT.RIGHT);
PropsUi.setLook(wlPath);
wlPath.setText(BaseMessages.getString(PKG,
"WorkflowExecutorDialog.Workflow.Label"));
FormData fdlJobformation = new FormData();
@@ -153,7 +170,7 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
fdlJobformation.right = new FormAttachment(middle, -margin);
wlPath.setLayoutData(fdlJobformation);
- Button wbBrowse = new Button(shell, SWT.PUSH);
+ wbBrowse = new Button(shell, SWT.PUSH);
PropsUi.setLook(wbBrowse);
wbBrowse.setText(BaseMessages.getString(PKG,
"WorkflowExecutorDialog.Browse.Label"));
FormData fdBrowse = new FormData();
@@ -170,13 +187,66 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
fdPath.right = new FormAttachment(wbBrowse, -margin);
wPath.setLayoutData(fdPath);
+ wbWorkflowNameInField = new Button(shell, SWT.CHECK);
+ PropsUi.setLook(wbWorkflowNameInField);
+ wbWorkflowNameInField.setText(
+ BaseMessages.getString(PKG,
"WorkflowExecutorDialog.WorkflowNameInField.Label"));
+ FormData fdWorkflowNameInField = new FormData();
+ fdWorkflowNameInField.left = new FormAttachment(middle, 0);
+ fdWorkflowNameInField.top = new FormAttachment(wPath, margin);
+ wbWorkflowNameInField.setLayoutData(fdWorkflowNameInField);
+ wbWorkflowNameInField.addSelectionListener(
+ new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ workflowExecutorMeta.setChanged();
+ activeWorkflowNameField();
+ }
+ });
+
+ wlWorkflowNameField = new Label(shell, SWT.RIGHT);
+ wlWorkflowNameField.setText(
+ BaseMessages.getString(PKG,
"WorkflowExecutorDialog.WorkflowNameField.Label"));
+ PropsUi.setLook(wlWorkflowNameField);
+ FormData fdlWorkflowNameField = new FormData();
+ fdlWorkflowNameField.left = new FormAttachment(0, 0);
+ fdlWorkflowNameField.right = new FormAttachment(middle, -margin);
+ fdlWorkflowNameField.top = new FormAttachment(wbWorkflowNameInField,
margin);
+ wlWorkflowNameField.setLayoutData(fdlWorkflowNameField);
+
+ wWorkflowNameField = new ComboVar(variables, shell, SWT.SINGLE | SWT.LEFT
| SWT.BORDER);
+ PropsUi.setLook(wWorkflowNameField);
+ wWorkflowNameField.addModifyListener(lsMod);
+ FormData fdWorkflowNameField = new FormData();
+ fdWorkflowNameField.left = new FormAttachment(middle, 0);
+ fdWorkflowNameField.top = new FormAttachment(wlWorkflowNameField, 0,
SWT.CENTER);
+ fdWorkflowNameField.right = new FormAttachment(100, 0);
+ wWorkflowNameField.setLayoutData(fdWorkflowNameField);
+ wWorkflowNameField.setEnabled(false);
+ wWorkflowNameField.addFocusListener(
+ new FocusListener() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ // Do nothing
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ Cursor busy = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
+ shell.setCursor(busy);
+ getFields();
+ shell.setCursor(null);
+ busy.dispose();
+ }
+ });
+
wlRunConfiguration = new Label(shell, SWT.RIGHT);
wlRunConfiguration.setText(
BaseMessages.getString(PKG,
"WorkflowExecutorDialog.RunConfiguration.Label"));
PropsUi.setLook(wlRunConfiguration);
FormData fdlRunConfiguration = new FormData();
fdlRunConfiguration.left = new FormAttachment(0, 0);
- fdlRunConfiguration.top = new FormAttachment(wPath, margin);
+ fdlRunConfiguration.top = new FormAttachment(wWorkflowNameField, margin);
fdlRunConfiguration.right = new FormAttachment(middle, -margin);
wlRunConfiguration.setLayoutData(fdlRunConfiguration);
@@ -221,6 +291,42 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
return transformName;
}
+ private void getFields() {
+ if (!gotPreviousFields) {
+ try {
+ String field = wWorkflowNameField.getText();
+ IRowMeta r = pipelineMeta.getPrevTransformFields(variables,
transformName);
+ if (r != null) {
+ wWorkflowNameField.setItems(r.getFieldNames());
+ }
+ if (field != null) {
+ wWorkflowNameField.setText(field);
+ }
+ } catch (HopException ke) {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(PKG,
"WorkflowExecutorDialog.ErrorLoadingWorkflow.DialogTitle"),
+ BaseMessages.getString(
+ PKG,
"WorkflowExecutorDialog.ErrorLoadingWorkflow.DialogMessage"),
+ ke);
+ }
+ gotPreviousFields = true;
+ }
+ }
+
+ private void activeWorkflowNameField() {
+ wlWorkflowNameField.setEnabled(wbWorkflowNameInField.getSelection());
+ wWorkflowNameField.setEnabled(wbWorkflowNameInField.getSelection());
+ wPath.setEnabled(!wbWorkflowNameInField.getSelection());
+ wlPath.setEnabled(!wbWorkflowNameInField.getSelection());
+ wbBrowse.setEnabled(!wbWorkflowNameInField.getSelection());
+ if (wbWorkflowNameInField.getSelection()) {
+ wPath.setText("");
+ } else {
+ wWorkflowNameField.setText("");
+ }
+ }
+
private void selectWorkflowFile() {
String curFile = variables.resolve(wPath.getText());
@@ -308,6 +414,12 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
} else {
wRunConfiguration.setText(workflowExecutorMeta.getRunConfigurationName());
}
+
+
wbWorkflowNameInField.setSelection(workflowExecutorMeta.isFilenameInField());
+ if (workflowExecutorMeta.getFilenameField() != null) {
+ wWorkflowNameField.setText(workflowExecutorMeta.getFilenameField());
+ }
+ activeWorkflowNameField();
} catch (Exception e) {
LogChannel.UI.logError("Error getting workflow run configurations", e);
}
@@ -1038,19 +1150,24 @@ public class WorkflowExecutorDialog extends
BaseTransformDialog {
transformName = wTransformName.getText(); // return value
- try {
- loadWorkflow();
- } catch (HopException e) {
- new ErrorDialog(
- shell,
- BaseMessages.getString(
- PKG,
CONST_WORKFLOW_EXECUTOR_DIALOG_ERROR_LOADING_SPECIFIED_JOB_TITLE),
- BaseMessages.getString(
- PKG,
CONST_WORKFLOW_EXECUTOR_DIALOG_ERROR_LOADING_SPECIFIED_JOB_MESSAGE),
- e);
+ // No check if the workflow to be executed comes from the stream field.
+ if (!wbWorkflowNameInField.getSelection()) {
+ try {
+ loadWorkflow();
+ } catch (HopException e) {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(
+ PKG,
CONST_WORKFLOW_EXECUTOR_DIALOG_ERROR_LOADING_SPECIFIED_JOB_TITLE),
+ BaseMessages.getString(
+ PKG,
CONST_WORKFLOW_EXECUTOR_DIALOG_ERROR_LOADING_SPECIFIED_JOB_MESSAGE),
+ e);
+ }
}
workflowExecutorMeta.setFilename(wPath.getText());
+
workflowExecutorMeta.setFilenameInField(wbWorkflowNameInField.getSelection());
+ workflowExecutorMeta.setFilenameField(wWorkflowNameField.getText());
workflowExecutorMeta.setRunConfigurationName(wRunConfiguration.getText());
// Load the information on the tabs, optionally do some
diff --git
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
index f6f3b330bb..d1ba9c7e2a 100644
---
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
+++
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
@@ -93,6 +93,14 @@ public class WorkflowExecutorMeta
hopMetadataPropertyType = HopMetadataPropertyType.WORKFLOW_FILE)
private String filename;
+ /** Flag that indicate that workflow name is specified in a stream's field */
+ @HopMetadataProperty(key = "filenameInField")
+ private boolean filenameInField;
+
+ /** Name of the field containing the workflow file's name */
+ @HopMetadataProperty(key = "filenameField")
+ private String filenameField;
+
/**
* The number of input rows that are sent as result rows to the workflow in
one go, defaults to
* "1"
@@ -253,6 +261,7 @@ public class WorkflowExecutorMeta
parameters = new ArrayList<>();
resultRowsField = new ArrayList<>();
inheritingAllVariables = true;
+ filenameInField = false;
groupSize = "1";
groupField = "";
@@ -432,20 +441,41 @@ public class WorkflowExecutorMeta
IHopMetadataProvider metadataProvider,
IVariables variables)
throws HopException {
- WorkflowMeta mappingWorkflowMeta = null;
+ return loadWorkflowMeta(executorMeta, null, metadataProvider, variables);
+ }
+
+ /**
+ * Loads child workflow metadata from a file.
+ *
+ * @param explicitWorkflowFilename when non-empty, this path is loaded and
resolved instead of the
+ * filename stored on {@code executorMeta}. Use when the workflow path
is taken from an
+ * incoming row at runtime so multiple transform copies must not mutate
shared meta.
+ */
+ public static final synchronized WorkflowMeta loadWorkflowMeta(
+ WorkflowExecutorMeta executorMeta,
+ String explicitWorkflowFilename,
+ IHopMetadataProvider metadataProvider,
+ IVariables variables)
+ throws HopException {
+ String filenameToUse =
+ !Utils.isEmpty(explicitWorkflowFilename)
+ ? explicitWorkflowFilename
+ : executorMeta.getFilename();
CurrentDirectoryResolver r = new CurrentDirectoryResolver();
IVariables tmpSpace =
- r.resolveCurrentDirectory(
- variables, executorMeta.getParentTransformMeta(),
executorMeta.getFilename());
+ r.resolveCurrentDirectory(variables,
executorMeta.getParentTransformMeta(), filenameToUse);
- String realFilename = tmpSpace.resolve(executorMeta.getFilename());
+ String realFilename = tmpSpace.resolve(filenameToUse);
+ if (variables != null) {
+ realFilename = variables.resolve(realFilename);
+ }
// OK, load the meta-data from file...
//
// Don't set internal variables: they belong to the parent thread!
//
- mappingWorkflowMeta = new WorkflowMeta(variables, realFilename,
metadataProvider);
+ WorkflowMeta mappingWorkflowMeta = new WorkflowMeta(variables,
realFilename, metadataProvider);
LogChannel.GENERAL.logDetailed(
"Loaded workflow", "Workflow was loaded from XML file [" +
realFilename + "]");
diff --git
a/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
b/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
index f48a49c196..d5d8aac442 100644
---
a/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
+++
b/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
@@ -18,6 +18,7 @@
WorkflowExecutor.Description=This transform executes a Hop workflow, sets
parameters and passes rows.
WorkflowExecutor.Exception.GroupFieldNotFound=Group field ''{0}'' could not be
found in the input stream
WorkflowExecutor.Exception.UnableToFindField=Unable to find field ''{0}'' in
the input stream.
+WorkflowExecutor.Exception.UnableToLoadWorkflow=Unable to load the specified
workflow\: {0}
WorkflowExecutor.IncorrectDataTypePassed=The ''{0}'' data types passed from
the workflows result rows does not correspond to the specified ''{1}'' data
types. Make sure you are passing rows with the expected layout.
WorkflowExecutor.Name=Workflow executor
WorkflowExecutor.UnexpectedError=There was an unexpected error\:
@@ -72,6 +73,8 @@ WorkflowExecutorDialog.RunConfiguration.Label=Run
configuration
WorkflowExecutorDialog.Shell.Title=Workflow executor
WorkflowExecutorDialog.TransformName.Label=Transform name
WorkflowExecutorDialog.Workflow.Label=Workflow
+WorkflowExecutorDialog.WorkflowNameField.Label=Workflow field
+WorkflowExecutorDialog.WorkflowNameInField.Label=Workflow from field
WorkflowExecutorMeta.CheckResult.NoInputReceived=No input is received from
previous transforms
WorkflowExecutorMeta.CheckResult.NotReceivingAnyFields=No input fields are
received from previous transforms
WorkflowExecutorMeta.CheckResult.TransformReceivingFields=This transform is
receiving fields from previous transforms.
diff --git
a/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
index c45a1053a8..f15100fe1a 100644
---
a/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
+++
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
@@ -18,6 +18,8 @@
package org.apache.hop.pipeline.transforms.workflowexecutor;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
import org.junit.jupiter.api.Test;
@@ -31,6 +33,8 @@ class WorkflowExecutorMetaTest {
"/workflow-executor-transform.xml", WorkflowExecutorMeta.class);
assertEquals("${PROJECT_HOME}/loops/child-workflow-executor.hwf",
meta.getFilename());
+ assertFalse(meta.isFilenameInField());
+ assertEquals("", meta.getFilenameField() == null ? "" :
meta.getFilenameField());
assertEquals("ExecutionTime", meta.getExecutionTimeField());
assertEquals("ExecutionResult", meta.getExecutionResultField());
assertEquals("ExecutionNrErrors", meta.getExecutionNrErrorsField());
@@ -61,6 +65,8 @@ class WorkflowExecutorMetaTest {
WorkflowExecutorMeta clone = (WorkflowExecutorMeta) meta.clone();
assertEquals(clone.getFilename(), meta.getFilename());
+ assertEquals(clone.isFilenameInField(), meta.isFilenameInField());
+ assertEquals(clone.getFilenameField(), meta.getFilenameField());
assertEquals(clone.getExecutionTimeField(), meta.getExecutionTimeField());
assertEquals(clone.getExecutionResultField(),
meta.getExecutionResultField());
assertEquals(clone.getExecutionNrErrorsField(),
meta.getExecutionNrErrorsField());
@@ -81,4 +87,21 @@ class WorkflowExecutorMetaTest {
assertEquals(clone.getResultRowsField().size(),
meta.getResultRowsField().size());
assertEquals(clone.getParameters().size(), meta.getParameters().size());
}
+
+ @Test
+ void setDefaultDisablesFilenameInField() {
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ assertFalse(meta.isFilenameInField());
+ }
+
+ @Test
+ void testSerializationFilenameInField() throws Exception {
+ WorkflowExecutorMeta meta =
+ TransformSerializationTestUtil.testSerialization(
+ "/workflow-executor-fromfield-transform.xml",
WorkflowExecutorMeta.class);
+
+ assertTrue(meta.isFilenameInField());
+ assertEquals("wf_to_run", meta.getFilenameField());
+ }
}
diff --git
a/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorTest.java
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorTest.java
new file mode 100644
index 0000000000..4868c84cb1
--- /dev/null
+++
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package org.apache.hop.pipeline.transforms.workflowexecutor;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.net.URISyntaxException;
+import java.nio.file.Paths;
+import java.util.Objects;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Unit tests for {@link WorkflowExecutor} runtime behaviour. */
+class WorkflowExecutorTest {
+
+ @RegisterExtension
+ static final RestoreHopEngineEnvironmentExtension env =
+ new RestoreHopEngineEnvironmentExtension();
+
+ @BeforeEach
+ void initHop() throws HopException {
+ HopEnvironment.init();
+ }
+
+ private WorkflowExecutor newExecutor(WorkflowExecutorMeta meta,
WorkflowExecutorData data) {
+ TransformMeta transformMeta = mock(TransformMeta.class);
+ when(transformMeta.getName()).thenReturn("workflow_executor");
+ when(transformMeta.isPartitioned()).thenReturn(false);
+ PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+ when(pipelineMeta.findTransform(anyString())).thenReturn(transformMeta);
+ LocalPipelineEngine pipeline = spy(new LocalPipelineEngine());
+ WorkflowExecutor executor =
+ new WorkflowExecutor(transformMeta, meta, data, 0, pipelineMeta,
pipeline);
+ executor.setMetadataProvider(new MemoryMetadataProvider());
+ return executor;
+ }
+
+ @Test
+ void initFailsWhenStaticFilenameMissing() {
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(false);
+ meta.setFilename("");
+ meta.setRunConfigurationName("local");
+
+ WorkflowExecutor executor = newExecutor(meta, new WorkflowExecutorData());
+ assertFalse(executor.init());
+ }
+
+ @Test
+ void initSucceedsWhenFilenameComesFromField() {
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(true);
+ meta.setFilenameField("child_path");
+ meta.setFilename("/tmp/ignored-default.hwf");
+ meta.setRunConfigurationName("local");
+
+ WorkflowExecutor executor = newExecutor(meta, new WorkflowExecutorData());
+ assertTrue(executor.init());
+ }
+
+ @Test
+ void initFailsWhenFilenameFieldMissing() {
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(true);
+ meta.setFilenameField("");
+ meta.setRunConfigurationName("local");
+
+ WorkflowExecutor executor = newExecutor(meta, new WorkflowExecutorData());
+ assertFalse(executor.init());
+ }
+
+ @Test
+ void initLoadsChildWorkflowFromFilesystemPath() throws URISyntaxException {
+ String path =
+ Paths.get(
+ Objects.requireNonNull(
+ WorkflowExecutorTest.class.getResource(
+
"/org/apache/hop/pipeline/transforms/workflowexecutor/minimal-child.hwf"))
+ .toURI())
+ .toAbsolutePath()
+ .toString();
+
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(false);
+ meta.setFilename(path);
+ meta.setRunConfigurationName("local");
+
+ WorkflowExecutor executor = newExecutor(meta, new WorkflowExecutorData());
+ assertTrue(executor.init());
+ assertNotNull(executor.getData().executorWorkflowMeta);
+ }
+
+ @Test
+ void loadWorkflowMetaHonorsExplicitFilenameOverWrongMetaFilename()
+ throws HopException, URISyntaxException {
+ String goodPath =
+ Paths.get(
+ Objects.requireNonNull(
+ WorkflowExecutorTest.class.getResource(
+
"/org/apache/hop/pipeline/transforms/workflowexecutor/minimal-child.hwf"))
+ .toURI())
+ .toAbsolutePath()
+ .toString();
+
+ WorkflowExecutorMeta meta = new WorkflowExecutorMeta();
+ meta.setDefault();
+ meta.setFilename("/this/path/does/not/exist/bad-child.hwf");
+
+ WorkflowMeta loaded =
+ WorkflowExecutorMeta.loadWorkflowMeta(
+ meta, goodPath, new MemoryMetadataProvider(), new Variables());
+
+ assertNotNull(loaded);
+ assertFalse(loaded.getActions().isEmpty());
+ }
+}
diff --git a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
b/plugins/transforms/workflowexecutor/src/test/resources/org/apache/hop/pipeline/transforms/workflowexecutor/minimal-child.hwf
similarity index 52%
copy from integration-tests/transforms/main-0083-workflow-executor-test.hwf
copy to
plugins/transforms/workflowexecutor/src/test/resources/org/apache/hop/pipeline/transforms/workflowexecutor/minimal-child.hwf
index 895b54c5da..ef669b313a 100644
--- a/integration-tests/transforms/main-0083-workflow-executor-test.hwf
+++
b/plugins/transforms/workflowexecutor/src/test/resources/org/apache/hop/pipeline/transforms/workflowexecutor/minimal-child.hwf
@@ -18,15 +18,15 @@ limitations under the License.
-->
<workflow>
- <name>main-0083-workflow-executor-test</name>
+ <name>minimal-child</name>
<name_sync_with_filename>Y</name_sync_with_filename>
<description/>
<extended_description/>
<workflow_version/>
<created_user>-</created_user>
- <created_date>2020/12/07 13:03:35.562</created_date>
+ <created_date>2025/01/01 00:00:00.000</created_date>
<modified_user>-</modified_user>
- <modified_date>2020/12/07 13:03:35.562</modified_date>
+ <modified_date>2025/01/01 00:00:00.000</modified_date>
<parameters>
</parameters>
<actions>
@@ -45,8 +45,8 @@ limitations under the License.
<schedulerType>0</schedulerType>
<weekDay>1</weekDay>
<parallel>N</parallel>
- <xloc>160</xloc>
- <yloc>96</yloc>
+ <xloc>112</xloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
<action>
@@ -55,69 +55,18 @@ limitations under the License.
<type>SUCCESS</type>
<attributes/>
<parallel>N</parallel>
- <xloc>544</xloc>
- <yloc>96</yloc>
- <attributes_hac/>
- </action>
- <action>
- <name>Abort workflow</name>
- <description/>
- <type>ABORT</type>
- <attributes/>
- <always_log_rows>N</always_log_rows>
- <parallel>N</parallel>
- <xloc>320</xloc>
- <yloc>256</yloc>
- <attributes_hac/>
- </action>
- <action>
- <name>0083-workflow-executor-run-wf.hpl</name>
- <description/>
- <type>PIPELINE</type>
- <attributes/>
- <add_date>N</add_date>
- <add_time>N</add_time>
- <clear_files>N</clear_files>
- <clear_rows>N</clear_rows>
- <create_parent_folder>N</create_parent_folder>
- <exec_per_row>N</exec_per_row>
- <filename>${PROJECT_HOME}/0083-workflow-executor-run-wf.hpl</filename>
- <loglevel>Basic</loglevel>
- <parameters>
- <pass_all_parameters>Y</pass_all_parameters>
- </parameters>
- <params_from_previous>N</params_from_previous>
- <run_configuration>local</run_configuration>
- <set_append_logfile>N</set_append_logfile>
- <set_logfile>N</set_logfile>
- <wait_until_finished>Y</wait_until_finished>
- <parallel>N</parallel>
<xloc>320</xloc>
- <yloc>98</yloc>
+ <yloc>112</yloc>
<attributes_hac/>
</action>
</actions>
<hops>
<hop>
<from>START</from>
- <to>0083-workflow-executor-run-wf.hpl</to>
- <enabled>Y</enabled>
- <evaluation>Y</evaluation>
- <unconditional>Y</unconditional>
- </hop>
- <hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
<to>Success</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
- <unconditional>N</unconditional>
- </hop>
- <hop>
- <from>0083-workflow-executor-run-wf.hpl</from>
- <to>Abort workflow</to>
- <enabled>Y</enabled>
- <evaluation>N</evaluation>
- <unconditional>N</unconditional>
+ <unconditional>Y</unconditional>
</hop>
</hops>
<notepads>
diff --git
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-fromfield-transform.xml
similarity index 97%
copy from
plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
copy to
plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-fromfield-transform.xml
index 4064641e4a..0cc8e568ca 100644
---
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
+++
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-fromfield-transform.xml
@@ -29,6 +29,8 @@
</partitioning>
<run_configuration>local</run_configuration>
<filename>${PROJECT_HOME}/loops/child-workflow-executor.hwf</filename>
+ <filenameInField>Y</filenameInField>
+ <filenameField>wf_to_run</filenameField>
<group_size>1</group_size>
<group_field/>
<group_time/>
diff --git
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
index 4064641e4a..d2058dec6b 100644
---
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
+++
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
@@ -29,6 +29,8 @@
</partitioning>
<run_configuration>local</run_configuration>
<filename>${PROJECT_HOME}/loops/child-workflow-executor.hwf</filename>
+ <filenameInField>N</filenameInField>
+ <filenameField/>
<group_size>1</group_size>
<group_field/>
<group_time/>