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 8ea6adf8df Issue #8000 : Route unreadable XML files to error handling
(#8029)
8ea6adf8df is described below
commit 8ea6adf8df3f884168afdede1853a891e693b3da
Author: vbhanuchander-lang <[email protected]>
AuthorDate: Fri Aug 21 03:36:13 2026 -0400
Issue #8000 : Route unreadable XML files to error handling (#8029)
* Issue #8000 : Route unreadable XML files to error handling
Get data from XML aborted the whole pipeline when a file could not be
parsed. openNextFile() caught the failure, called stopAll() and
setErrors(1) unconditionally, without ever asking whether the transform
has an error hop -- so an invalid document killed the pipeline instead of
being diverted, while the same document read from a field was handled
correctly.
When error handling is enabled, send the file to the error stream and
carry on with the next one, matching what processPutRow already does for
the in-fields path. The file pointer is normally advanced before the
document is parsed, but a failure raised earlier -- resolving the file,
reading its size or attributes -- leaves it untouched, so step over the
file explicitly in that case rather than retrying it forever.
openNextFile() now declares HopTransformException because putError()
throws it; every caller already propagates HopException.
Adds a file-based counterpart to testErrorHandlingContinuesOnBadXml: a
good file, an unparseable one and another good file. Both good files are
still read and the bad one produces a single error row. Reverting the
change fails the test with one reported error, which is the behaviour in
the report.
* add integration tests
---------
Co-authored-by: Hans Van Akelyen <[email protected]>
---
.../xml/0019-get-data-from-xml-error-handling.hpl | 336 +++++++++++++++++++++
.../files/getxmldata-error-handling/01-good.xml | 28 ++
.../getxmldata-error-handling/02-unparseable.xml | 22 ++
.../files/getxmldata-error-handling/03-good.xml | 24 ++
.../04-unparseable-last.xml | 22 ++
.../main-0019-get-data-from-xml-error-handling.hwf | 111 +++++++
.../transforms/xml/getxmldata/GetXmlData.java | 26 +-
.../transforms/xml/getxmldata/GetXMLDataTest.java | 78 +++++
8 files changed, 643 insertions(+), 4 deletions(-)
diff --git a/integration-tests/xml/0019-get-data-from-xml-error-handling.hpl
b/integration-tests/xml/0019-get-data-from-xml-error-handling.hpl
new file mode 100644
index 0000000000..6ea1339271
--- /dev/null
+++ b/integration-tests/xml/0019-get-data-from-xml-error-handling.hpl
@@ -0,0 +1,336 @@
+<?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>0019-get-data-from-xml-error-handling</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Issue #8000: unparseable XML files must go to the error
stream instead of killing the pipeline. 04-unparseable-last.xml also covers a
failure on the very last file of the list.</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/08/21 09:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/08/21 09:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>read xml folder</from>
+ <to>count good rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>read xml folder</from>
+ <to>count error rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>count good rows</from>
+ <to>check 3 good rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>check 3 good rows</from>
+ <to>good row count mismatch</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>count error rows</from>
+ <to>check 2 error rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>check 2 error rows</from>
+ <to>error row count mismatch</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>read xml folder</name>
+ <type>getXMLData</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <IsAFile>N</IsAFile>
+ <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+ <IsInFields>N</IsInFields>
+ <XmlField/>
+ <addresultfile>N</addresultfile>
+ <doNotFailIfNoFile>N</doNotFailIfNoFile>
+ <encoding>UTF-8</encoding>
+ <extensionFieldName/>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <element_type>Node</element_type>
+ <format/>
+ <group/>
+ <length>-1</length>
+ <name>tag1</name>
+ <precision>-1</precision>
+ <repeat>N</repeat>
+ <result_type>valueof</result_type>
+ <trim_type>none</trim_type>
+ <type>String</type>
+ <xPath>tag1</xPath>
+ </field>
+ <field>
+ <currency/>
+ <decimal/>
+ <element_type>Node</element_type>
+ <format/>
+ <group/>
+ <length>-1</length>
+ <name>tag2</name>
+ <precision>-1</precision>
+ <repeat>N</repeat>
+ <result_type>valueof</result_type>
+ <trim_type>none</trim_type>
+ <type>String</type>
+ <xPath>tag2</xPath>
+ </field>
+ </fields>
+ <file>
+ <exclude_filemask/>
+ <file_required>N</file_required>
+ <filemask>.*\.xml$</filemask>
+ <include_subfolders>N</include_subfolders>
+ <name>${PROJECT_HOME}/files/getxmldata-error-handling</name>
+ </file>
+ <hiddenFieldName/>
+ <ignorecomments>N</ignorecomments>
+ <include>N</include>
+ <include_field/>
+ <lastModificationTimeFieldName/>
+ <limit>0</limit>
+ <loopxpath>/root/row</loopxpath>
+ <namespaceaware>N</namespaceaware>
+ <pathFieldName/>
+ <prunePath/>
+ <readurl>N</readurl>
+ <rootUriNameFieldName/>
+ <rownum>N</rownum>
+ <rownum_field/>
+ <shortFileFieldName/>
+ <sizeFieldName/>
+ <uriNameFieldName/>
+ <usetoken>N</usetoken>
+ <validating>N</validating>
+ <attributes/>
+ <GUI>
+ <xloc>144</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>count good rows</name>
+ <type>MemoryGroupBy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <aggregate>goodRows</aggregate>
+ <subject>tag1</subject>
+ <type>COUNT_ANY</type>
+ </field>
+ </fields>
+ <give_back_row>Y</give_back_row>
+ <group>
+</group>
+ <attributes/>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>check 3 good rows</name>
+ <type>FilterRows</type>
+ <description>Both readable files must still be read: 2 rows from
01-good.xml and 1 from 03-good.xml.</description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <compare>
+ <condition>
+ <conditions>
+</conditions>
+ <function>=</function>
+ <leftvalue>goodRows</leftvalue>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <value>
+ <isnull>N</isnull>
+ <length>-1</length>
+ <mask>####0;-####0</mask>
+ <name>constant</name>
+ <precision>0</precision>
+ <text>3</text>
+ <type>Integer</type>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>528</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>good row count mismatch</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>
+ <row_threshold>0</row_threshold>
+ <attributes/>
+ <GUI>
+ <xloc>720</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>count error rows</name>
+ <type>MemoryGroupBy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <aggregate>errorRows</aggregate>
+ <subject>errorDescription</subject>
+ <type>COUNT_ANY</type>
+ </field>
+ </fields>
+ <give_back_row>Y</give_back_row>
+ <group>
+</group>
+ <attributes/>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>check 2 error rows</name>
+ <type>FilterRows</type>
+ <description>02-unparseable.xml and 04-unparseable-last.xml must produce
one error row each.</description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <compare>
+ <condition>
+ <conditions>
+</conditions>
+ <function>=</function>
+ <leftvalue>errorRows</leftvalue>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <value>
+ <isnull>N</isnull>
+ <length>-1</length>
+ <mask>####0;-####0</mask>
+ <name>constant</name>
+ <precision>0</precision>
+ <text>2</text>
+ <type>Integer</type>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>528</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>error row count mismatch</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>
+ <row_threshold>0</row_threshold>
+ <attributes/>
+ <GUI>
+ <xloc>720</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ <error>
+ <source_transform>read xml folder</source_transform>
+ <target_transform>count error rows</target_transform>
+ <is_enabled>Y</is_enabled>
+ <nr_valuename>errorCount</nr_valuename>
+ <descriptions_valuename>errorDescription</descriptions_valuename>
+ <fields_valuename>errorFields</fields_valuename>
+ <codes_valuename>errorCodes</codes_valuename>
+ <max_errors/>
+ <max_pct_errors/>
+ <min_pct_rows/>
+ </error>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/xml/files/getxmldata-error-handling/01-good.xml
b/integration-tests/xml/files/getxmldata-error-handling/01-good.xml
new file mode 100644
index 0000000000..880e78de8c
--- /dev/null
+++ b/integration-tests/xml/files/getxmldata-error-handling/01-good.xml
@@ -0,0 +1,28 @@
+<!--
+
+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.
+
+-->
+<root>
+ <row>
+ <tag1>good-1-a</tag1>
+ <tag2>value-1-a</tag2>
+ </row>
+ <row>
+ <tag1>good-1-b</tag1>
+ <tag2>value-1-b</tag2>
+ </row>
+</root>
diff --git
a/integration-tests/xml/files/getxmldata-error-handling/02-unparseable.xml
b/integration-tests/xml/files/getxmldata-error-handling/02-unparseable.xml
new file mode 100644
index 0000000000..dbcb448279
--- /dev/null
+++ b/integration-tests/xml/files/getxmldata-error-handling/02-unparseable.xml
@@ -0,0 +1,22 @@
+<!--
+
+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.
+
+-->
+<root>
+ <row>
+ <tag1>never-read</tag1>
+ <tag2>never-read</tag2>
diff --git a/integration-tests/xml/files/getxmldata-error-handling/03-good.xml
b/integration-tests/xml/files/getxmldata-error-handling/03-good.xml
new file mode 100644
index 0000000000..12c70875d2
--- /dev/null
+++ b/integration-tests/xml/files/getxmldata-error-handling/03-good.xml
@@ -0,0 +1,24 @@
+<!--
+
+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.
+
+-->
+<root>
+ <row>
+ <tag1>good-3-a</tag1>
+ <tag2>value-3-a</tag2>
+ </row>
+</root>
diff --git
a/integration-tests/xml/files/getxmldata-error-handling/04-unparseable-last.xml
b/integration-tests/xml/files/getxmldata-error-handling/04-unparseable-last.xml
new file mode 100644
index 0000000000..0bac2d0b27
--- /dev/null
+++
b/integration-tests/xml/files/getxmldata-error-handling/04-unparseable-last.xml
@@ -0,0 +1,22 @@
+<!--
+
+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.
+
+-->
+<root>
+ <row>
+ <tag1>never-read-last</tag1>
+ <tag2>never-read-last</tag2>
diff --git
a/integration-tests/xml/main-0019-get-data-from-xml-error-handling.hwf
b/integration-tests/xml/main-0019-get-data-from-xml-error-handling.hwf
new file mode 100644
index 0000000000..0cd72375d0
--- /dev/null
+++ b/integration-tests/xml/main-0019-get-data-from-xml-error-handling.hwf
@@ -0,0 +1,111 @@
+<?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.
+
+-->
+<workflow>
+ <name>main-0019-get-data-from-xml-error-handling</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/08/21 09:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/08/21 09:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>START</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <hour>12</hour>
+ <intervalMinutes>60</intervalMinutes>
+ <intervalSeconds>0</intervalSeconds>
+ <minutes>0</minutes>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <weekDay>1</weekDay>
+ <parallel>N</parallel>
+ <xloc>64</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0019-get-data-from-xml-error-handling.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}/0019-get-data-from-xml-error-handling.hpl</filename>
+ <logext/>
+ <logfile/>
+ <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>272</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>error handling broken</name>
+ <description/>
+ <type>ABORT</type>
+ <attributes/>
+ <message>The pipeline reported errors: an unparseable XML file did not
go to the error stream (issue #8000).</message>
+ <parallel>N</parallel>
+ <xloc>512</xloc>
+ <yloc>160</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>START</from>
+ <to>0019-get-data-from-xml-error-handling.hpl</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>0019-get-data-from-xml-error-handling.hpl</from>
+ <to>error handling broken</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git
a/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXmlData.java
b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXmlData.java
index 6353b07fda..6cbd0d1327 100644
---
a/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXmlData.java
+++
b/plugins/transforms/xml/src/main/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXmlData.java
@@ -37,6 +37,7 @@ import org.apache.hop.core.Const;
import org.apache.hop.core.ResultFile;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopRuntimeException;
+import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.fileinput.FileInputList;
import org.apache.hop.core.io.CountingInputStream;
import org.apache.hop.core.row.IRowMeta;
@@ -698,7 +699,8 @@ public class GetXmlData extends
BaseTransform<GetXmlDataMeta, GetXmlDataData> {
return true;
}
- private boolean openNextFile() {
+ private boolean openNextFile() throws HopTransformException {
+ int fileNrOnEntry = data.filenr;
try {
if (data.filenr >= data.files.nrOfFiles()) {
// finished processing!
@@ -790,13 +792,29 @@ public class GetXmlData extends
BaseTransform<GetXmlDataMeta, GetXmlDataData> {
}
}
} catch (Exception e) {
- logError(
+ String message =
BaseMessages.getString(
PKG,
"GetXMLData.Log.UnableToOpenFile",
"" + data.filenr,
- data.file.toString(),
- e.toString()));
+ data.file == null ? "" : data.file.toString(),
+ e.toString());
+
+ if (getTransformMeta().isDoingErrorHandling()) {
+ // Reading the same document from a field already diverts a parse
failure to the error hop.
+ // Do the same for a file: send this one to error handling and carry
on with the next file
+ // rather than killing the pipeline.
+ logBasic(message);
+ putError(data.outputRowMeta, buildEmptyRow(), 1, message, null,
"GetXMLData002");
+ if (data.filenr <= fileNrOnEntry) {
+ // The failure happened before the file pointer moved on, so step
over this file
+ // explicitly -- otherwise we would retry it forever.
+ data.filenr = fileNrOnEntry + 1;
+ }
+ return openNextFile();
+ }
+
+ logError(message);
stopAll();
setErrors(1);
return false;
diff --git
a/plugins/transforms/xml/src/test/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXMLDataTest.java
b/plugins/transforms/xml/src/test/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXMLDataTest.java
index 51016a0db0..3f36a09da0 100644
---
a/plugins/transforms/xml/src/test/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXMLDataTest.java
+++
b/plugins/transforms/xml/src/test/java/org/apache/hop/pipeline/transforms/xml/getxmldata/GetXMLDataTest.java
@@ -20,7 +20,10 @@ package org.apache.hop.pipeline.transforms.xml.getxmldata;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.hop.core.Const;
@@ -49,6 +52,7 @@ import
org.apache.hop.pipeline.transforms.xml.RowTransformCollector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
/** Test class for the "Get XML Data" transform. */
@ExtendWith(RestoreHopEnvironmentExtension.class)
@@ -569,4 +573,78 @@ class GetXMLDataTest {
errorCollector.getRowsError().getFirst().getData()[0],
"error row keeps the offending input value");
}
+
+ /**
+ * The same guarantee has to hold when the XML comes from a file rather than
a field. A file that
+ * cannot be parsed must be diverted to the error stream and the remaining
files still read,
+ * instead of the transform calling stopAll() and taking the whole pipeline
down with it.
+ * Regression test for #8000.
+ */
+ @Test
+ void testErrorHandlingContinuesOnBadXmlFile(@TempDir Path tempDir) throws
Exception {
+ // Read in name order: a good file, an unparseable one, then another good
file.
+ Files.writeString(tempDir.resolve("1-good.xml"), getXML1());
+ Files.writeString(tempDir.resolve("2-bad.xml"), "<Level1><Level2><Props>");
+ Files.writeString(tempDir.resolve("3-good.xml"), getXML2());
+
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.setName("getxmldata-file-errorhandling");
+
+ PluginRegistry registry = PluginRegistry.getInstance();
+
+ // Get XML Data, reading every .xml in the temporary folder
+ String getXMLDataName = "get xml data transform";
+ GetXmlDataMeta gxdm = new GetXmlDataMeta();
+ gxdm.setEncoding(Const.UTF_8);
+ gxdm.setAFile(false);
+ gxdm.setInFields(false);
+ gxdm.setLoopXPath("Level1/Level2/Props");
+ gxdm.setInputFields(java.util.Arrays.asList(createXmlDataFields()));
+ gxdm.setFilesList(
+ Collections.singletonList(
+ new GetXmlFileItem(tempDir.toString(), ".*\\.xml$", "", "N",
"N")));
+ String getXMLDataPid = registry.getPluginId(TransformPluginType.class,
gxdm);
+ TransformMeta getXMLDataTransform = new TransformMeta(getXMLDataPid,
getXMLDataName, gxdm);
+ pipelineMeta.addTransform(getXMLDataTransform);
+
+ // Main output
+ String dummyMainName = "dummy main";
+ DummyMeta dmMain = new DummyMeta();
+ String dummyMainPid = registry.getPluginId(TransformPluginType.class,
dmMain);
+ TransformMeta dummyMain = new TransformMeta(dummyMainPid, dummyMainName,
dmMain);
+ pipelineMeta.addTransform(dummyMain);
+ pipelineMeta.addPipelineHop(new PipelineHopMeta(getXMLDataTransform,
dummyMain));
+
+ // Error output
+ String dummyErrorName = "dummy error";
+ DummyMeta dmError = new DummyMeta();
+ String dummyErrorPid = registry.getPluginId(TransformPluginType.class,
dmError);
+ TransformMeta dummyError = new TransformMeta(dummyErrorPid,
dummyErrorName, dmError);
+ pipelineMeta.addTransform(dummyError);
+ pipelineMeta.addPipelineHop(new PipelineHopMeta(getXMLDataTransform,
dummyError));
+
+ TransformErrorMeta errorMeta = new TransformErrorMeta(getXMLDataTransform,
dummyError);
+ errorMeta.setEnabled(true);
+ getXMLDataTransform.setTransformErrorMeta(errorMeta);
+
+ Pipeline pipeline = new LocalPipelineEngine(pipelineMeta);
+ pipeline.prepareExecution();
+
+ RowTransformCollector errorCollector = new RowTransformCollector();
+ pipeline.getTransform(getXMLDataName, 0).addRowListener(errorCollector);
+ RowTransformCollector mainCollector = new RowTransformCollector();
+ pipeline.getTransform(dummyMainName, 0).addRowListener(mainCollector);
+
+ pipeline.startThreads();
+ pipeline.waitUntilFinished();
+
+ // Without the fix the transform calls stopAll() and the pipeline reports
an error.
+ assertEquals(0, pipeline.getResult().getNrErrors(), "transform should not
report errors");
+
+ // Both readable files are still read: 2 rows from the first, 1 from the
third.
+ assertEquals(3, mainCollector.getRowsWritten().size(), "good files are
still read");
+
+ // The unreadable file produces exactly one error row.
+ assertEquals(1, errorCollector.getRowsError().size(), "bad file goes to
error handling");
+ }
}