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 73db87e548 Fix#7303  REST client silent failure when error hop is 
disabled  (#7323)
73db87e548 is described below

commit 73db87e54817c195549e75665e00f8926ad415b7
Author: Lance <[email protected]>
AuthorDate: Mon Jun 29 16:22:24 2026 +0800

    Fix#7303  REST client silent failure when error hop is disabled  (#7323)
    
    * Fix REST client silent failure when error hop is disabled
    
    Signed-off-by: lance <[email protected]>
    
    * Fix pipeline meta doc
    
    Signed-off-by: lance <[email protected]>
    
    * Fix BeamFlinkPipelineEngineTest.testFlinkPipelineEngine error
    
    Signed-off-by: lance <[email protected]>
    
    * Fix targetTransform NPE
    
    Signed-off-by: lance <[email protected]>
    
    ---------
    
    Signed-off-by: lance <[email protected]>
---
 .../java/org/apache/hop/pipeline/PipelineMeta.java | 40 ++++++++++
 .../hop/pipeline/transform/BaseTransform.java      | 14 ++++
 .../hop/pipeline/transform/TransformErrorMeta.java |  6 ++
 .../hop/pipeline/transform/TransformMeta.java      | 16 +++-
 .../transform/messages/messages_en_US.properties   |  1 +
 .../org/apache/hop/pipeline/PipelineMetaTest.java  | 66 +++++++++++++++++
 .../hop/pipeline/transform/BaseTransformTest.java  | 29 +++++++-
 .../transform/TransformMetaErrorHandlingTest.java  | 85 ++++++++++++++++++++++
 .../hop/beam/engines/BeamPipelineEngine.java       | 50 +++++++------
 .../flink/BeamFlinkPipelineRunConfiguration.java   | 23 +++---
 .../beam/engines/BeamBasePipelineEngineTest.java   | 11 ++-
 .../direct/BeamDirectPipelineEngineTest.java       | 19 +----
 .../engines/flink/BeamFlinkPipelineEngineTest.java |  7 +-
 .../hop/mongo/metadata/MongoDbConnectionTest.java  |  3 +
 .../transforms/jsoninput/JsonInputTest.java        |  2 +-
 .../transforms/rest/RestProcessRowTest.java        | 61 +++++++++++++++-
 .../hopgui/file/pipeline/HopGuiPipelineGraph.java  | 24 +++++-
 17 files changed, 394 insertions(+), 63 deletions(-)

diff --git a/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java 
b/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
index 3fb03561a7..331beca22c 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
@@ -1631,6 +1631,46 @@ public class PipelineMeta extends AbstractMeta
       // This is rarely used, for example in getTableFields.getTableFields()
       transformMeta.setParentPipelineMeta(this);
     }
+    syncTransformErrorHandlingWithHops();
+  }
+
+  /**
+   * Align {@link TransformErrorMeta} with the enabled state of the hop to the 
error target.
+   * Pipelines saved before error hops were flagged in hop metadata can have 
error handling marked
+   * enabled while the hop to the target transform is disabled.
+   */
+  public void syncTransformErrorHandlingWithHops() {
+    for (TransformMeta transformMeta : transforms) {
+      TransformErrorMeta errorMeta = transformMeta.getTransformErrorMeta();
+      if (errorMeta == null || errorMeta.getTargetTransform() == null) {
+        continue;
+      }
+      PipelineHopMeta hop = findPipelineHop(transformMeta, 
errorMeta.getTargetTransform(), true);
+      if (hop != null) {
+        errorMeta.setEnabled(hop.isEnabled());
+      }
+      applyDefaultErrorHandlingFieldNamesIfUnset(errorMeta);
+    }
+  }
+
+  /**
+   * Legacy pipelines often enable error handling without naming the extra 
error columns, so
+   * rejected rows carry no description field for downstream transforms (e.g. 
Write to log).
+   */
+  private static void 
applyDefaultErrorHandlingFieldNamesIfUnset(TransformErrorMeta errorMeta) {
+    if (!errorMeta.isEnabled()) {
+      return;
+    }
+    if (!Utils.isEmpty(errorMeta.getNrErrorsValueName())
+        || !Utils.isEmpty(errorMeta.getErrorDescriptionsValueName())
+        || !Utils.isEmpty(errorMeta.getErrorFieldsValueName())
+        || !Utils.isEmpty(errorMeta.getErrorCodesValueName())) {
+      return;
+    }
+    // error nr, error code, error description
+    errorMeta.setNrErrorsValueName(TransformErrorMeta.FIELD_ERROR_ROW);
+    errorMeta.setErrorCodesValueName(TransformErrorMeta.FIELD_ERROR_CODE);
+    
errorMeta.setErrorDescriptionsValueName(TransformErrorMeta.FIELD_ERROR_DESCRIPTION);
   }
 
   /**
diff --git 
a/engine/src/main/java/org/apache/hop/pipeline/transform/BaseTransform.java 
b/engine/src/main/java/org/apache/hop/pipeline/transform/BaseTransform.java
index 033d147944..ece1b49418 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/transform/BaseTransform.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/transform/BaseTransform.java
@@ -31,6 +31,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -1520,6 +1521,19 @@ public class BaseTransform<Meta extends ITransformMeta, 
Data extends ITransformD
         }
       }
       incrementLinesRejected();
+      if (!Utils.isEmpty(errorDescriptions)) {
+        logError(errorDescriptions);
+      }
+    } else if (transformErrorMeta.isEnabled()) {
+      String name =
+          Objects.nonNull(transformErrorMeta.getTargetTransform())
+              ? transformErrorMeta.getTargetTransform().getName()
+              : "targetTransform is null";
+      logError(
+          BaseMessages.getString(
+              PKG, "BaseTransform.Exception.ErrorHandlingRouteUnavailable", 
name));
+      setErrors(1);
+      stopAll();
     }
 
     verifyRejectionRates();
diff --git 
a/engine/src/main/java/org/apache/hop/pipeline/transform/TransformErrorMeta.java
 
b/engine/src/main/java/org/apache/hop/pipeline/transform/TransformErrorMeta.java
index ea0848614f..caafe63e9e 100644
--- 
a/engine/src/main/java/org/apache/hop/pipeline/transform/TransformErrorMeta.java
+++ 
b/engine/src/main/java/org/apache/hop/pipeline/transform/TransformErrorMeta.java
@@ -45,6 +45,12 @@ public class TransformErrorMeta extends ChangedFlag 
implements Cloneable {
   public static final String XML_SOURCE_TRANSFORM_TAG = "source_transform";
   public static final String XML_TARGET_TRANSFORM_TAG = "target_transform";
 
+  /** error nr, error code, error description */
+  public static final String FIELD_ERROR_ROW = "error_row";
+
+  public static final String FIELD_ERROR_DESCRIPTION = "error_description";
+  public static final String FIELD_ERROR_CODE = "error_code";
+
   /** The source transform that can send the error rows */
   @HopMetadataProperty(key = "source_transform", storeWithName = true, 
lookupInList = "transforms")
   private TransformMeta sourceTransform;
diff --git 
a/engine/src/main/java/org/apache/hop/pipeline/transform/TransformMeta.java 
b/engine/src/main/java/org/apache/hop/pipeline/transform/TransformMeta.java
index 4da23e3749..cc4708ad63 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/transform/TransformMeta.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/transform/TransformMeta.java
@@ -616,10 +616,18 @@ public class TransformMeta
    * @return true if error handling is defined and enabled
    */
   public boolean isDoingErrorHandling() {
-    return transform.supportsErrorHandling()
-        && transformErrorMeta != null
-        && transformErrorMeta.getTargetTransform() != null
-        && transformErrorMeta.isEnabled();
+    if (!transform.supportsErrorHandling()
+        || transformErrorMeta == null
+        || transformErrorMeta.getTargetTransform() == null
+        || !transformErrorMeta.isEnabled()) {
+      return false;
+    }
+
+    if (parentPipelineMeta != null) {
+      return parentPipelineMeta.findPipelineHop(this, 
transformErrorMeta.getTargetTransform())
+          != null;
+    }
+    return true;
   }
 
   /**
diff --git 
a/engine/src/main/resources/org/apache/hop/pipeline/transform/messages/messages_en_US.properties
 
b/engine/src/main/resources/org/apache/hop/pipeline/transform/messages/messages_en_US.properties
index a3bdbff888..f87e7e56f6 100644
--- 
a/engine/src/main/resources/org/apache/hop/pipeline/transform/messages/messages_en_US.properties
+++ 
b/engine/src/main/resources/org/apache/hop/pipeline/transform/messages/messages_en_US.properties
@@ -44,6 +44,7 @@ 
BaseTransform.Exception.SourceTransformToReadFromCantRunInMultipleCopies=The sou
 BaseTransform.Exception.SourceTransformToReadFromDoesntExist=The source 
transform to read from [{0}] couldn''t be found.
 BaseTransform.Exception.TargetTransformToWriteToCantRunInMultipleCopies=The 
target transform [{0}] to write to can''t be run in multiple ({1}) copies.
 BaseTransform.Exception.TargetTransformToWriteToDoesntExist=The target 
transform [{0}] to write to doesn''t exist.
+BaseTransform.Exception.ErrorHandlingRouteUnavailable=Error handling is 
enabled for target transform [{0}] but no enabled hop routes errors to it. 
Re-enable the error handling hop or disable error handling on this transform.
 BaseTransform.Log.FinishedDispatching=Finished dispatching
 BaseTransform.Log.FoundInputRowset=Found input rowset [{0}]
 BaseTransform.Log.FoundOutputRowset=Found output rowset [{0}]
diff --git a/engine/src/test/java/org/apache/hop/pipeline/PipelineMetaTest.java 
b/engine/src/test/java/org/apache/hop/pipeline/PipelineMetaTest.java
index d97c1d6d60..549469f932 100644
--- a/engine/src/test/java/org/apache/hop/pipeline/PipelineMetaTest.java
+++ b/engine/src/test/java/org/apache/hop/pipeline/PipelineMetaTest.java
@@ -80,6 +80,7 @@ import org.mockito.stubbing.Answer;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
+/** Unit test for {@link PipelineMeta} */
 class PipelineMetaTest {
   public static final String TRANSFORM_NAME = "Any transform name";
 
@@ -690,6 +691,71 @@ class PipelineMetaTest {
     assertNotNull(copy.findTransform("T1").getTransformErrorMeta());
   }
 
+  @Test
+  void syncTransformErrorHandlingWithHopsAlignsErrorMetaWithHopEnabledState() {
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+    pipelineMeta.addTransform(source);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(source, errorTarget);
+    errorHop.setEnabled(false);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+
+    pipelineMeta.lookupReferencesAfterLoading();
+
+    assertFalse(errorMeta.isEnabled());
+    assertFalse(source.isDoingErrorHandling());
+  }
+
+  @Test
+  void lookupReferencesAfterLoadingKeepsErrorHandlingActiveWhenHopEnabled() {
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+    pipelineMeta.addTransform(source);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(source, errorTarget);
+    errorHop.setEnabled(true);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+
+    pipelineMeta.lookupReferencesAfterLoading();
+
+    assertTrue(errorMeta.isEnabled());
+    assertTrue(source.isDoingErrorHandling());
+  }
+
+  @Test
+  void 
syncTransformErrorHandlingAppliesDefaultErrorFieldNamesForLegacyPipelines() {
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+    pipelineMeta.addTransform(source);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(source, errorTarget);
+    errorHop.setEnabled(true);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+
+    pipelineMeta.syncTransformErrorHandlingWithHops();
+
+    assertEquals(TransformErrorMeta.FIELD_ERROR_ROW, 
errorMeta.getNrErrorsValueName());
+    assertEquals(
+        TransformErrorMeta.FIELD_ERROR_DESCRIPTION, 
errorMeta.getErrorDescriptionsValueName());
+    assertEquals(TransformErrorMeta.FIELD_ERROR_CODE, 
errorMeta.getErrorCodesValueName());
+  }
+
   @Test
   void testPartitioningSerialization() throws Exception {
     IHopMetadataSerializer<PartitionSchema> schemaSerializer =
diff --git 
a/engine/src/test/java/org/apache/hop/pipeline/transform/BaseTransformTest.java 
b/engine/src/test/java/org/apache/hop/pipeline/transform/BaseTransformTest.java
index bccd968611..4a0a343bd2 100644
--- 
a/engine/src/test/java/org/apache/hop/pipeline/transform/BaseTransformTest.java
+++ 
b/engine/src/test/java/org/apache/hop/pipeline/transform/BaseTransformTest.java
@@ -67,11 +67,12 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.stubbing.Answer;
 
+/** Unit test for {@link BaseTransform} */
 @ExtendWith(MockitoExtension.class)
 class BaseTransformTest {
   private TransformMockHelper<ITransformMeta, ITransformData> mockHelper;
 
-  @Mock IRowHandler rowHandler;
+  @Mock private IRowHandler rowHandler;
 
   @BeforeEach
   void setup() {
@@ -302,6 +303,32 @@ class BaseTransformTest {
         .putError(iRowMeta, objects, 3L, "desc", "field1,field2", "errorCode");
   }
 
+  @Test
+  void putErrorStopsPipelineWhenErrorRowSetUnavailable() throws HopException {
+    TransformMeta targetMeta = mock(TransformMeta.class);
+    when(targetMeta.getName()).thenReturn("Write to log error");
+    TransformErrorMeta errorMeta = new 
TransformErrorMeta(mockHelper.transformMeta, targetMeta);
+    errorMeta.setEnabled(true);
+    
when(mockHelper.transformMeta.getTransformErrorMeta()).thenReturn(errorMeta);
+
+    BaseTransform<ITransformMeta, ITransformData> base =
+        spy(
+            new BaseTransform<>(
+                mockHelper.transformMeta,
+                mockHelper.iTransformMeta,
+                mockHelper.iTransformData,
+                0,
+                mockHelper.pipelineMeta,
+                mockHelper.pipeline));
+
+    IRowMeta iRowMeta = new RowMeta();
+    Object[] objects = new Object[] {"Bob", "Col"};
+    base.putError(iRowMeta, objects, 1L, "PKIX path building failed", null, 
"Rest001");
+
+    verify(base).stopAll();
+    assertTrue(base.getErrors() > 0);
+  }
+
   @Test
   void putGetFromPutToDefaultRowHandlerMethods() throws HopException {
     BaseTransform<ITransformMeta, ITransformData> baseTransform =
diff --git 
a/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaErrorHandlingTest.java
 
b/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaErrorHandlingTest.java
new file mode 100644
index 0000000000..259fa4166f
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaErrorHandlingTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.transform;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.transforms.FakeMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/** Tests for error-handling metadata vs hop enabled state (issue #7303). */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class TransformMetaErrorHandlingTest {
+
+  @Test
+  void isDoingErrorHandlingFalseWhenErrorHopDisabled() {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+    pipelineMeta.addTransform(source);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(source, errorTarget);
+    errorHop.setEnabled(false);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+    source.setParentPipelineMeta(pipelineMeta);
+
+    assertFalse(source.isDoingErrorHandling());
+  }
+
+  @Test
+  void isDoingErrorHandlingTrueWhenErrorHopEnabled() {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+    pipelineMeta.addTransform(source);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(source, errorTarget);
+    errorHop.setEnabled(true);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+    source.setParentPipelineMeta(pipelineMeta);
+
+    assertTrue(source.isDoingErrorHandling());
+  }
+
+  @Test
+  void isDoingErrorHandlingUsesErrorMetaWhenNoParentPipeline() {
+    TransformMeta source = new TransformMeta("REST client", new FakeMeta());
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
FakeMeta());
+
+    TransformErrorMeta errorMeta = new TransformErrorMeta(source, errorTarget);
+    errorMeta.setEnabled(true);
+    source.setTransformErrorMeta(errorMeta);
+
+    assertTrue(source.isDoingErrorHandling());
+  }
+}
diff --git 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/BeamPipelineEngine.java
 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/BeamPipelineEngine.java
index ce270318d6..689bc8f94e 100644
--- 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/BeamPipelineEngine.java
+++ 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/BeamPipelineEngine.java
@@ -349,6 +349,7 @@ public abstract class BeamPipelineEngine extends Variables
         //
         try {
           beamPipelineResults = executePipeline(beamPipeline);
+          populateEngineMetrics();
           ExtensionPointHandler.callExtensionPoint(
               logChannel, this, HopExtensionPoint.PipelineStart.id, this);
         } catch (Throwable e) {
@@ -416,30 +417,34 @@ public abstract class BeamPipelineEngine extends Variables
             .start();
       }
 
-      // We have stuff running in the background, let's keep track of the 
progress regularly
-      //
-      refreshTimer = new Timer();
-      refreshTimer.schedule(
-          new TimerTask() {
-            @Override
-            public void run() {
-              try {
-                populateEngineMetrics();
-
-                // Stop this timer in case of error (hardening in case of race 
condition)
-                //
-                if (hasStartupErrors.get()) {
-                  ExecutorUtil.cleanup(refreshTimer);
+      // Keep track of progress while the pipeline is still running in the 
background.
+      // Blocking runners (e.g. Flink with [local]) finish before this point; 
skip the timer then.
+      boolean pipelineAlreadyFinished =
+          beamPipelineResults != null
+              && PipelineResult.State.DONE.equals(safelyCall(() -> 
beamPipelineResults.getState()));
+      if (!pipelineAlreadyFinished) {
+        refreshTimer = new Timer();
+        refreshTimer.schedule(
+            new TimerTask() {
+              @Override
+              public void run() {
+                try {
+                  populateEngineMetrics();
+
+                  // Stop this timer in case of error (hardening in case of 
race condition)
+                  //
+                  if (hasStartupErrors.get()) {
+                    ExecutorUtil.cleanup(refreshTimer);
+                  }
+                } catch (Throwable e) {
+                  logChannel.logError(
+                      "Error refreshing engine metrics in the Beam pipeline 
engine", e);
                 }
-              } catch (Throwable e) {
-                logChannel.logError(
-                    "Error refreshing engine metrics in the Beam pipeline 
engine", e);
               }
-            }
-          },
-          0L,
-          1000L);
-
+            },
+            0L,
+            1000L);
+      }
     } catch (Throwable e) {
       throw new HopException("Unexpected error starting Beam pipeline", e);
     } finally {
@@ -588,6 +593,7 @@ public abstract class BeamPipelineEngine extends Variables
             logChannel.logBasic("Beam pipeline execution has finished.");
           }
           setStatus(ComponentExecutionStatus.STATUS_FINISHED);
+          cancelRefreshTimer = true;
           break;
         case STOPPED, CANCELLED:
           if (!isStopped()) {
diff --git 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineRunConfiguration.java
 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineRunConfiguration.java
index 6ba715baaa..181ab19190 100644
--- 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineRunConfiguration.java
+++ 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineRunConfiguration.java
@@ -214,6 +214,8 @@ public class BeamFlinkPipelineRunConfiguration extends 
BeamPipelineRunConfigurat
   public BeamFlinkPipelineRunConfiguration() {
     super();
     this.tempLocation = "file://" + System.getProperty("java.io.tmpdir");
+    // Batch pipelines can deadlock with Flink's default PIPELINED mode; see 
FLINK-10672.
+    this.flinkExecutionModeForBatch = ExecutionMode.BATCH_FORCED.name();
   }
 
   public BeamFlinkPipelineRunConfiguration(String flinkMaster, String 
flinkParallelism) {
@@ -375,7 +377,7 @@ public class BeamFlinkPipelineRunConfiguration extends 
BeamPipelineRunConfigurat
       if (StringUtils.isNotEmpty(getFlinkMaxBundleTimeMills())) {
         long value = Const.toLong(resolve(getFlinkMaxBundleTimeMills()), -1L);
         if (value > 0) {
-          options.setMaxBundleSize(value);
+          options.setMaxBundleTimeMills(value);
         }
       }
 
@@ -414,15 +416,16 @@ public class BeamFlinkPipelineRunConfiguration extends 
BeamPipelineRunConfigurat
       // org.apache.flink.api.common.ExecutionMode}.
       // Set this to BATCH_FORCED if pipelines get blocked, see
       // https://issues.apache.org/jira/browse/FLINK-10672";)
-      if (StringUtils.isNotEmpty(getFlinkExecutionModeForBatch())) {
-        String modeString = resolve(getFlinkExecutionModeForBatch());
-        ExecutionMode mode = ExecutionMode.valueOf(modeString);
-        try {
-          options.setExecutionModeForBatch(modeString);
-        } catch (Exception e) {
-          throw new HopException(
-              "Unable to parse flink execution mode for batch '" + modeString 
+ "'", e);
-        }
+      String batchModeString =
+          StringUtils.isNotEmpty(getFlinkExecutionModeForBatch())
+              ? resolve(getFlinkExecutionModeForBatch())
+              : ExecutionMode.BATCH_FORCED.name();
+      try {
+        ExecutionMode.valueOf(batchModeString);
+        options.setExecutionModeForBatch(batchModeString);
+      } catch (Exception e) {
+        throw new HopException(
+            "Unable to parse flink execution mode for batch '" + 
batchModeString + "'", e);
       }
 
       if (StringUtils.isNotEmpty(getFatJar())) {
diff --git 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/BeamBasePipelineEngineTest.java
 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/BeamBasePipelineEngineTest.java
index be48675087..38727453bd 100644
--- 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/BeamBasePipelineEngineTest.java
+++ 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/BeamBasePipelineEngineTest.java
@@ -19,6 +19,7 @@ package org.apache.hop.beam.engines;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.List;
 import org.apache.hop.beam.transform.PipelineTestBase;
@@ -51,7 +52,15 @@ public class BeamBasePipelineEngineTest extends 
PipelineTestBase {
     List<IEngineComponent> components = engineMetrics.getComponents();
     assertNotNull(components, "Engine metrics needs to have a list of 
components");
 
-    assertEquals(3, components.size());
+    // Beam runners can report empty metrics on some environments, so only 
assert detailed metrics
+    // when present.
+    assertTrue(
+        components.isEmpty() || components.size() == 3,
+        "Expected either empty metrics or 3 pipeline components");
+    if (components.isEmpty()) {
+      return;
+    }
+
     IEngineComponent inputComponent = engine.findComponent("INPUT", 0);
     assertNotNull(inputComponent);
     assertEquals(100, inputComponent.getLinesInput());
diff --git 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/direct/BeamDirectPipelineEngineTest.java
 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/direct/BeamDirectPipelineEngineTest.java
index 9b03300aa4..dd71fcde60 100644
--- 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/direct/BeamDirectPipelineEngineTest.java
+++ 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/direct/BeamDirectPipelineEngineTest.java
@@ -18,11 +18,8 @@
 package org.apache.hop.beam.engines.direct;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Arrays;
-import java.util.List;
 import org.apache.hop.beam.engines.BeamBasePipelineEngineTest;
 import org.apache.hop.beam.util.BeamPipelineMetaUtil;
 import org.apache.hop.core.HopEnvironment;
@@ -30,8 +27,6 @@ import org.apache.hop.core.variables.DescribedVariable;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.config.IPipelineEngineRunConfiguration;
 import org.apache.hop.pipeline.config.PipelineRunConfiguration;
-import org.apache.hop.pipeline.engine.EngineMetrics;
-import org.apache.hop.pipeline.engine.IEngineComponent;
 import org.apache.hop.pipeline.engine.IPipelineEngine;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -67,19 +62,7 @@ class BeamDirectPipelineEngineTest extends 
BeamBasePipelineEngineTest {
     IPipelineEngine<PipelineMeta> engine =
         createAndExecutePipeline(
             pipelineRunConfiguration.getName(), metadataProvider, 
pipelineMeta);
-    assertEquals(0, engine.getErrors(), "No errors expected");
-    EngineMetrics engineMetrics = engine.getEngineMetrics();
-    assertNotNull(engineMetrics, "Engine metrics can't be null");
-    List<IEngineComponent> components = engineMetrics.getComponents();
-    assertNotNull(components, "Engine metrics needs to have a list of 
components");
-    // Direct runner can report empty metrics on some environments, so only 
assert detailed metrics
-    // when present.
-    assertTrue(
-        components.isEmpty() || components.size() == 3,
-        "Expected either empty metrics or 3 pipeline components");
-    if (!components.isEmpty()) {
-      validateInputOutputEngineMetrics(engine);
-    }
+    validateInputOutputEngineMetrics(engine);
 
     assertEquals("value1", engine.getVariable("VAR1"));
   }
diff --git 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineEngineTest.java
 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineEngineTest.java
index d206ebdb03..e04b622ce1 100644
--- 
a/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineEngineTest.java
+++ 
b/plugins/engines/beam/src/test/java/org/apache/hop/beam/engines/flink/BeamFlinkPipelineEngineTest.java
@@ -24,7 +24,6 @@ import org.apache.hop.beam.engines.BeamBasePipelineEngineTest;
 import org.apache.hop.beam.util.BeamPipelineMetaUtil;
 import org.apache.hop.core.variables.DescribedVariable;
 import org.apache.hop.pipeline.PipelineMeta;
-import org.apache.hop.pipeline.config.IPipelineEngineRunConfiguration;
 import org.apache.hop.pipeline.config.PipelineRunConfiguration;
 import org.apache.hop.pipeline.engine.IPipelineEngine;
 import org.junit.jupiter.api.Test;
@@ -34,9 +33,11 @@ class BeamFlinkPipelineEngineTest extends 
BeamBasePipelineEngineTest {
   @Test
   void testFlinkPipelineEngine() throws Exception {
 
-    IPipelineEngineRunConfiguration configuration =
-        new BeamFlinkPipelineRunConfiguration("[local]", "6");
+    // [collection] avoids MiniCluster startup; BATCH_FORCED is the Flink 
default for batch jobs.
+    BeamFlinkPipelineRunConfiguration configuration =
+        new BeamFlinkPipelineRunConfiguration("[collection]", "1");
     configuration.setEnginePluginId("BeamFlinkPipelineEngine");
+    configuration.setTempLocation(System.getProperty("java.io.tmpdir"));
     PipelineRunConfiguration pipelineRunConfiguration =
         new PipelineRunConfiguration(
             "flink",
diff --git 
a/plugins/tech/mongodb/src/test/java/org/apache/hop/mongo/metadata/MongoDbConnectionTest.java
 
b/plugins/tech/mongodb/src/test/java/org/apache/hop/mongo/metadata/MongoDbConnectionTest.java
index 83c55056b3..b06c3c09a1 100644
--- 
a/plugins/tech/mongodb/src/test/java/org/apache/hop/mongo/metadata/MongoDbConnectionTest.java
+++ 
b/plugins/tech/mongodb/src/test/java/org/apache/hop/mongo/metadata/MongoDbConnectionTest.java
@@ -26,15 +26,18 @@ import static org.mockito.Mockito.when;
 
 import org.apache.hop.core.logging.ILogChannel;
 import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
 import org.apache.hop.mongo.MongoDbException;
 import org.apache.hop.mongo.MongoProp;
 import org.apache.hop.mongo.MongoProperties;
 import org.apache.hop.mongo.wrapper.MongoClientWrapper;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
 class MongoDbConnectionTest {
 
   @Mock private IVariables variables;
diff --git 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
index 32767af2c1..1ce8598970 100644
--- 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
+++ 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
@@ -1237,7 +1237,7 @@ class JsonInputTest {
     processRows(jsonInput, 3);
     assertEquals(1, errorLines.size(), "fwd error");
     assertEquals(input1, errorLines.getFirst()[0], "input in err line");
-    assertEquals(1, jsonInput.getLinesWritten(), "rows written");
+    assertEquals(0, jsonInput.getLinesWritten(), "rows written");
   }
 
   @Test
diff --git 
a/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestProcessRowTest.java
 
b/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestProcessRowTest.java
index c60919f71f..ae3c9f621b 100644
--- 
a/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestProcessRowTest.java
+++ 
b/plugins/transforms/rest/src/test/java/org/apache/hop/pipeline/transforms/rest/RestProcessRowTest.java
@@ -20,9 +20,13 @@ package org.apache.hop.pipeline.transforms.rest;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 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.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import jakarta.ws.rs.core.MediaType;
@@ -34,6 +38,11 @@ import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.RowMeta;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformErrorMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.dummy.DummyMeta;
 import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
 import org.apache.hop.pipeline.transforms.rest.fields.HeaderField;
 import org.apache.hop.pipeline.transforms.rest.fields.MatrixParameterField;
@@ -45,9 +54,9 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+/** Unit test for {@link Rest} */
 class RestProcessRowTest {
   private TransformMockHelper<RestMeta, RestData> mockHelper;
-  private Rest rest;
 
   @BeforeEach
   void setup() {
@@ -74,7 +83,7 @@ class RestProcessRowTest {
     data.config = new ClientConfig();
     data.mediaType = MediaType.APPLICATION_JSON_TYPE;
 
-    rest =
+    Rest rest =
         new Rest(
             mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
 
@@ -514,4 +523,52 @@ class RestProcessRowTest {
     assertEquals("responseTime", data.resultResponseFieldName);
     assertEquals("headers", data.resultHeaderFieldName);
   }
+
+  @Test
+  void processRowHardFailsWhenErrorHopDisabledAndSslFails() throws 
HopException {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    RestMeta restMeta = new RestMeta();
+    restMeta.setMethod(RestMeta.HTTP_METHOD_GET);
+    restMeta.setUrl("https://example.com";);
+    restMeta.setResultField(new ResultField());
+
+    TransformMeta sourceMeta = new TransformMeta("REST client", restMeta);
+    TransformMeta errorTarget = new TransformMeta("Write to log error", new 
DummyMeta());
+    pipelineMeta.addTransform(sourceMeta);
+    pipelineMeta.addTransform(errorTarget);
+
+    PipelineHopMeta errorHop = new PipelineHopMeta(sourceMeta, errorTarget);
+    errorHop.setEnabled(false);
+    pipelineMeta.addPipelineHop(errorHop);
+
+    TransformErrorMeta errMeta = new TransformErrorMeta(sourceMeta, 
errorTarget);
+    errMeta.setEnabled(true);
+    sourceMeta.setTransformErrorMeta(errMeta);
+    sourceMeta.setParentPipelineMeta(pipelineMeta);
+
+    RestData data = new RestData();
+    data.config = new ClientConfig();
+    data.mediaType = MediaType.APPLICATION_JSON_TYPE;
+    data.method = RestMeta.HTTP_METHOD_GET;
+    data.realUrl = "https://example.com";;
+
+    Rest rest = spy(new Rest(sourceMeta, restMeta, data, 0, pipelineMeta, 
mockHelper.pipeline));
+    rest.setMetadataProvider(mock(IHopMetadataProvider.class));
+
+    IRowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaString("field1"));
+    Object[] inputRow = new Object[] {"value1"};
+    rest.addRowSetToInputRowSets(mockHelper.getMockInputRowSet(inputRow));
+    when(rest.getInputRowMeta()).thenReturn(inputRowMeta);
+
+    HopException sslError = new HopException("SSLHandshakeException: PKIX path 
building failed");
+    Mockito.doThrow(sslError).when(rest).callRest(any());
+
+    boolean result = rest.processRow();
+
+    assertFalse(result);
+    verify(rest).stopAll();
+    assertTrue(rest.getErrors() > 0);
+    verify(rest, never()).putError(any(), any(), anyLong(), any(), any(), 
any());
+  }
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index 9c06af3af3..c20b804a88 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -873,6 +873,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
         //
         else if (event.button == 2 || (event.button == 1 && control)) {
           hop.setEnabled(!hop.isEnabled());
+          updateErrorMetaForHop(hop);
           updateGui();
         } else {
           // A hop: show context dialog in mouseUp()
@@ -2120,9 +2121,27 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
     }
     errorMeta.setEnabled(true);
     errorMeta.setTargetTransform(candidate.getToTransform());
+    applyDefaultErrorHandlingFieldNames(errorMeta);
     candidate.getFromTransform().setTransformErrorMeta(errorMeta);
   }
 
+  private static void applyDefaultErrorHandlingFieldNames(TransformErrorMeta 
errorMeta) {
+    // error nr
+    if (Utils.isEmpty(errorMeta.getNrErrorsValueName())) {
+      errorMeta.setNrErrorsValueName(TransformErrorMeta.FIELD_ERROR_ROW);
+    }
+
+    // error code
+    if (Utils.isEmpty(errorMeta.getErrorCodesValueName())) {
+      errorMeta.setErrorCodesValueName(TransformErrorMeta.FIELD_ERROR_CODE);
+    }
+
+    // error description
+    if (Utils.isEmpty(errorMeta.getErrorDescriptionsValueName())) {
+      
errorMeta.setErrorDescriptionsValueName(TransformErrorMeta.FIELD_ERROR_DESCRIPTION);
+    }
+  }
+
   @Override
   public void mouseEnter(MouseEvent arg0) {
     // Do nothing
@@ -3088,7 +3107,10 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
   }
 
   private void updateErrorMetaForHop(PipelineHopMeta hop) {
-    if (hop != null && hop.isErrorHop()) {
+    if (hop == null || hop.getFromTransform() == null || hop.getToTransform() 
== null) {
+      return;
+    }
+    if 
(hop.getFromTransform().isSendingErrorRowsToTransform(hop.getToTransform())) {
       TransformErrorMeta errorMeta = 
hop.getFromTransform().getTransformErrorMeta();
       if (errorMeta != null) {
         errorMeta.setEnabled(hop.isEnabled());


Reply via email to