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");
+  }
 }

Reply via email to