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 def7d844e7 guard extension point, fixes #8239 (#8241)
def7d844e7 is described below

commit def7d844e72ed61a6573bed6d64f32f8c405001d
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Sep 2 18:18:42 2026 +0200

    guard extension point, fixes #8239 (#8241)
---
 .../apache/hop/www/GetPipelineImageServlet.java    |  4 +-
 .../apache/hop/www/GetWorkflowImageServlet.java    |  4 +-
 .../output-0015/extraction/exported-pipeline.txt   |  2 +
 .../apache/hop/lint/LintCanvasOverlayHelper.java   | 25 +++++++
 .../lint/PipelineLintTotalsPainterExtension.java   |  7 +-
 .../lint/WorkflowLintTotalsPainterExtension.java   |  7 +-
 .../hop/lint/LintTotalsOverlayHeadlessTest.java    | 85 ++++++++++++++++++++++
 7 files changed, 128 insertions(+), 6 deletions(-)

diff --git 
a/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java 
b/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java
index 3b97db9964..d586273c47 100644
--- a/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java
@@ -96,7 +96,9 @@ public class GetPipelineImageServlet extends BaseHttpServlet 
implements IHopServ
         OutputStream out = response.getOutputStream();
         out.write(svgStream.toByteArray());
       }
-    } catch (Exception e) {
+    } catch (Exception | Error e) {
+      // A GUI plugin painting on the canvas without SWT natives available 
throws SWTError, which
+      // extends Error directly: catching Exception alone let it escape as a 
raw Jetty 500 page.
       logError("Error building SVG image of pipeline", e);
       sendSafeError(
           response,
diff --git 
a/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java 
b/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java
index 591100de94..c2f74049f4 100644
--- a/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java
@@ -102,7 +102,9 @@ public class GetWorkflowImageServlet extends 
BaseHttpServlet implements IHopServ
           out.write(svgStream.toByteArray());
         }
       }
-    } catch (Exception e) {
+    } catch (Exception | Error e) {
+      // A GUI plugin painting on the canvas without SWT natives available 
throws SWTError, which
+      // extends Error directly: catching Exception alone let it escape as a 
raw Jetty 500 page.
       logError("Error building SVG image of workflow", e);
       sendSafeError(
           response,
diff --git 
a/integration-tests/hop_server/output-0015/extraction/exported-pipeline.txt 
b/integration-tests/hop_server/output-0015/extraction/exported-pipeline.txt
new file mode 100644
index 0000000000..392d968cc2
--- /dev/null
+++ b/integration-tests/hop_server/output-0015/extraction/exported-pipeline.txt
@@ -0,0 +1,2 @@
+message
+exported-pipeline-8234
diff --git 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayHelper.java
 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayHelper.java
index fabe304189..4aec2ac15b 100644
--- 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayHelper.java
+++ 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayHelper.java
@@ -23,7 +23,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.hop.core.gui.IGc;
+import org.apache.hop.core.logging.LogChannel;
 import org.apache.hop.core.util.Utils;
+import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.hopgui.HopGui;
 
 /** Groups lint results for canvas overlays and draws severity badges on 
pipeline/workflow icons. */
 public final class LintCanvasOverlayHelper {
@@ -161,6 +164,28 @@ public final class LintCanvasOverlayHelper {
     return totalWidth;
   }
 
+  /**
+   * The native zoom factor to draw the canvas overlays with, or {@code null} 
when there is no user
+   * interface to draw them in. The painter extension points also run on Hop 
Server, which renders
+   * pipeline and workflow SVG images without a UI: asking {@link PropsUi} for 
the zoom factor there
+   * loads the SWT natives and fails hard, with an UnsatisfiedLinkError when 
GTK is not installed.
+   */
+  public static Float overlayZoomFactor() {
+    try {
+      // peekInstance, not getInstance: the latter builds a HopGui, and 
building one creates an SWT
+      // Shell, which on a server fails outright instead of simply saying "no 
UI here".
+      if (HopGui.peekInstance() == null) {
+        return null;
+      }
+      return (float) PropsUi.getNativeZoomFactor();
+    } catch (Exception | Error e) {
+      // SWT throws SWTError, which extends Error directly rather than 
LinkageError, so catching
+      // Exception alone let it through, all the way up into the servlet 
rendering the image.
+      LogChannel.GENERAL.logDetailed("No user interface to draw the lint 
canvas overlay in: " + e);
+      return null;
+    }
+  }
+
   public static boolean isEnabled() {
     try {
       LinterConfigPlugin config = LinterConfigPlugin.getInstance();
diff --git 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineLintTotalsPainterExtension.java
 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineLintTotalsPainterExtension.java
index d6a28b0da6..7648d3a303 100644
--- 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineLintTotalsPainterExtension.java
+++ 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PipelineLintTotalsPainterExtension.java
@@ -26,7 +26,6 @@ import org.apache.hop.core.logging.ILogChannel;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.PipelinePainter;
-import org.apache.hop.ui.core.PropsUi;
 
 /**
  * Draws the lint error/warning/info totals as a fixed overlay in the top-left 
of the pipeline
@@ -66,7 +65,11 @@ public class PipelineLintTotalsPainterExtension implements 
IExtensionPoint<Pipel
         return;
       }
 
-      float nativeZoom = (float) PropsUi.getNativeZoomFactor();
+      // No SWT user interface (Hop Server rendering an SVG image): skip the 
canvas overlay.
+      Float nativeZoom = LintCanvasOverlayHelper.overlayZoomFactor();
+      if (nativeZoom == null) {
+        return;
+      }
 
       // Draw in logical widget coordinates (scale = native zoom) so the 
overlay stays fixed in
       // the top-left regardless of canvas pan/zoom, and lines up with mouse 
event coordinates.
diff --git 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowLintTotalsPainterExtension.java
 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowLintTotalsPainterExtension.java
index 0767fd5ff3..ede30a288d 100644
--- 
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowLintTotalsPainterExtension.java
+++ 
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowLintTotalsPainterExtension.java
@@ -24,7 +24,6 @@ import org.apache.hop.core.gui.DPoint;
 import org.apache.hop.core.gui.IGc;
 import org.apache.hop.core.logging.ILogChannel;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.workflow.WorkflowMeta;
 import org.apache.hop.workflow.WorkflowPainter;
 
@@ -66,7 +65,11 @@ public class WorkflowLintTotalsPainterExtension implements 
IExtensionPoint<Workf
         return;
       }
 
-      float nativeZoom = (float) PropsUi.getNativeZoomFactor();
+      // No SWT user interface (Hop Server rendering an SVG image): skip the 
canvas overlay.
+      Float nativeZoom = LintCanvasOverlayHelper.overlayZoomFactor();
+      if (nativeZoom == null) {
+        return;
+      }
 
       gc.setTransform(0f, 0f, nativeZoom);
       int width =
diff --git 
a/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintTotalsOverlayHeadlessTest.java
 
b/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintTotalsOverlayHeadlessTest.java
new file mode 100644
index 0000000000..7270f108bd
--- /dev/null
+++ 
b/plugins/misc/lint/src/test/java/org/apache/hop/lint/LintTotalsOverlayHeadlessTest.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.lint;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.hop.core.gui.DPoint;
+import org.apache.hop.core.gui.IGc;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.PipelinePainter;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.WorkflowPainter;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The lint totals overlay is a canvas feature, but the painter extension 
points drawing it also run
+ * on Hop Server, which renders pipeline and workflow SVG images without a 
user interface. Asking
+ * SWT for the native zoom factor there throws an Error rather than an 
Exception, which escaped
+ * every catch on the way out and left /hop/pipelineImage answering an HTTP 
500 (issue #8239).
+ */
+public class LintTotalsOverlayHeadlessTest {
+
+  @Test
+  public void overlayZoomFactorWithoutHopGuiReturnsNull() {
+    assertNull(HopGui.peekInstance(), "precondition: no GUI in this test JVM");
+
+    assertNull(LintCanvasOverlayHelper.overlayZoomFactor());
+
+    assertNull(HopGui.peekInstance(), "asking for the zoom factor must not 
build a HopGui");
+  }
+
+  @Test
+  public void pipelineTotalsOverlayIsSkippedWithoutUserInterface() throws 
Exception {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    pipelineMeta.setFilename("test.hpl");
+
+    IGc gc = mock(IGc.class);
+    PipelinePainter painter = mock(PipelinePainter.class);
+    when(painter.getPipelineMeta()).thenReturn(pipelineMeta);
+    when(painter.getGc()).thenReturn(gc);
+    when(painter.getOffset()).thenReturn(new DPoint(0, 0));
+
+    new PipelineLintTotalsPainterExtension()
+        .callExtensionPoint(mock(ILogChannel.class), mock(IVariables.class), 
painter);
+
+    verifyNoInteractions(gc);
+  }
+
+  @Test
+  public void workflowTotalsOverlayIsSkippedWithoutUserInterface() throws 
Exception {
+    WorkflowMeta workflowMeta = new WorkflowMeta();
+    workflowMeta.setFilename("test.hwf");
+
+    IGc gc = mock(IGc.class);
+    WorkflowPainter painter = mock(WorkflowPainter.class);
+    when(painter.getWorkflowMeta()).thenReturn(workflowMeta);
+    when(painter.getGc()).thenReturn(gc);
+    when(painter.getOffset()).thenReturn(new DPoint(0, 0));
+
+    new WorkflowLintTotalsPainterExtension()
+        .callExtensionPoint(mock(ILogChannel.class), mock(IVariables.class), 
painter);
+
+    verifyNoInteractions(gc);
+  }
+}

Reply via email to