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