You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2026/03/20 18:04:15 UTC
(hop) branch main updated: [SONAR] fix security issues and improve coverage (#6843)
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 0bc234116c [SONAR] fix security issues and improve coverage (#6843)
0bc234116c is described below
commit 0bc234116c3096e465217709b26b7457bdc31fbc
Author: Hans Van Akelyen <ha...@gmail.com>
AuthorDate: Fri Mar 20 19:04:09 2026 +0100
[SONAR] fix security issues and improve coverage (#6843)
---
.../java/org/apache/hop/www/AddExportServlet.java | 24 +--
.../java/org/apache/hop/www/BaseHttpServlet.java | 68 ++++++++-
.../java/org/apache/hop/www/BodyHttpServlet.java | 25 ++-
.../apache/hop/www/GetExecutionInfoServlet.java | 18 ++-
.../apache/hop/www/GetPipelineImageServlet.java | 12 +-
.../apache/hop/www/GetPipelineStatusServlet.java | 14 +-
.../java/org/apache/hop/www/GetRootServlet.java | 2 +-
.../java/org/apache/hop/www/GetStatusServlet.java | 84 ++++++-----
.../apache/hop/www/GetWorkflowImageServlet.java | 24 ++-
.../apache/hop/www/GetWorkflowStatusServlet.java | 10 +-
.../java/org/apache/hop/www/HopServerServlet.java | 8 +-
.../org/apache/hop/www/HopServerSingleton.java | 4 +-
.../main/java/org/apache/hop/www/PipelineMap.java | 6 +-
.../org/apache/hop/www/SniffTransformServlet.java | 12 +-
.../main/java/org/apache/hop/www/WebResult.java | 2 +
.../java/org/apache/hop/www/WebServiceServlet.java | 11 +-
.../apache/hop/www/jaxrs/HopServerResource.java | 4 +
.../org/apache/hop/www/jaxrs/PipelineResource.java | 4 +
.../org/apache/hop/www/jaxrs/WorkflowResource.java | 4 +
.../org/apache/hop/www/BaseHttpServletTest.java | 167 +++++++++++++++++++++
.../org/apache/hop/www/BodyHttpServletTest.java | 146 ++++++++++++++++++
...va => DefaultWebServerShutdownHandlerTest.java} | 25 +--
...t.java => GetExecutionInfoServletTypeTest.java} | 29 +---
.../hop/www/GetPipelineImageServletTest.java | 72 +++++++++
.../org/apache/hop/www/GetRootServletTest.java | 22 ++-
.../hop/www/GetWorkflowImageServletTest.java | 72 +++++++++
...est.java => HopServerCommandExceptionTest.java} | 35 ++---
.../apache/hop/www/HopServerObjectEntryTest.java | 64 ++++++++
.../hop/www/HopServerPluginServletSmokeTest.java | 55 +++++++
.../org/apache/hop/www/HopServerServletTest.java | 124 +++++++++++++++
.../org/apache/hop/www/HopServerSingletonTest.java | 58 +++++++
.../org/apache/hop/www/HopServerStatusTest.java | 55 +++++++
...ServletTest.java => IHopServerServletTest.java} | 26 +---
.../java/org/apache/hop/www/PipelineMapTest.java | 82 ++++++++++
.../apache/hop/www/SocketPortAllocationTest.java | 62 ++++++++
.../org/apache/hop/www/SslConfigurationTest.java | 83 ++++++++++
...ervletTest.java => StatusServletUtilsTest.java} | 27 +---
.../java/org/apache/hop/www/WebResultTest.java | 11 ++
.../apache/hop/www/WebServerShutdownHookTest.java | 65 ++++++++
.../java/org/apache/hop/www/WorkflowMapTest.java | 102 +++++++++++++
.../java/org/apache/hop/www/jaxrs/NVPairTest.java | 44 ++++++
.../apache/hop/www/jaxrs/PipelineStatusTest.java | 48 ++++++
.../org/apache/hop/www/jaxrs/ServerStatusTest.java | 41 +++++
.../apache/hop/www/jaxrs/WorkflowStatusTest.java | 36 +++++
.../org/apache/hop/www/service/WebServiceTest.java | 66 ++++++++
.../apache/hop/www/async/AsyncStatusServlet.java | 16 +-
46 files changed, 1764 insertions(+), 205 deletions(-)
diff --git a/engine/src/main/java/org/apache/hop/www/AddExportServlet.java b/engine/src/main/java/org/apache/hop/www/AddExportServlet.java
index 2f20374d71..bdc32c38bf 100644
--- a/engine/src/main/java/org/apache/hop/www/AddExportServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/AddExportServlet.java
@@ -28,7 +28,6 @@ import java.io.Serial;
import java.util.UUID;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.vfs2.FileObject;
-import org.apache.hop.core.Const;
import org.apache.hop.core.annotations.HopServerServlet;
import org.apache.hop.core.logging.LoggingObjectType;
import org.apache.hop.core.logging.SimpleLoggingObject;
@@ -93,10 +92,6 @@ public class AddExportServlet extends BaseHttpServlet implements IHopServerPlugi
if (out == null) {
return;
}
- InputStream in = request.getInputStream(); // read from the client
- if (log.isDetailed()) {
- logDetailed("Encoding: " + request.getCharacterEncoding());
- }
boolean isWorkflow =
TYPE_WORKFLOW.equalsIgnoreCase(
@@ -105,13 +100,18 @@ public class AddExportServlet extends BaseHttpServlet implements IHopServerPlugi
StringEscapeUtils.escapeHtml(request.getParameter(PARAMETER_LOAD)); // the resource to load
response.setContentType("text/xml");
- out.print(XmlHandler.getXmlHeader());
-
response.setStatus(HttpServletResponse.SC_OK);
OutputStream outputStream = null;
try {
+ InputStream in = request.getInputStream(); // read from the client
+ if (log.isDetailed()) {
+ logDetailed("Encoding: " + request.getCharacterEncoding());
+ }
+
+ out.print(XmlHandler.getXmlHeader());
+
FileObject tempFile =
HopVfs.createTempFile("export", ".zip", System.getProperty("java.io.tmpdir"));
outputStream = HopVfs.getOutputStream(tempFile, false);
@@ -235,10 +235,16 @@ public class AddExportServlet extends BaseHttpServlet implements IHopServerPlugi
out.println(new WebResult(WebResult.STRING_OK, fileUrl, serverObjectId));
} catch (Exception ex) {
- out.println(new WebResult(WebResult.STRING_ERROR, Const.getStackTracker(ex)));
+ logError("Add export failed", ex);
+ out.println(
+ new WebResult(WebResult.STRING_ERROR, "Export failed. See server log for details."));
} finally {
if (outputStream != null) {
- outputStream.close();
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ logError("Failed to close export output stream", e);
+ }
}
}
}
diff --git a/engine/src/main/java/org/apache/hop/www/BaseHttpServlet.java b/engine/src/main/java/org/apache/hop/www/BaseHttpServlet.java
index 913b0f8b25..42c1c05f2b 100644
--- a/engine/src/main/java/org/apache/hop/www/BaseHttpServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/BaseHttpServlet.java
@@ -23,8 +23,10 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serial;
+import java.nio.charset.StandardCharsets;
import lombok.Getter;
import lombok.Setter;
import org.apache.hop.core.Const;
@@ -32,6 +34,7 @@ import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.xml.XmlHandler;
import org.apache.http.entity.ContentType;
public class BaseHttpServlet extends HttpServlet {
@@ -43,7 +46,8 @@ public class BaseHttpServlet extends HttpServlet {
@Setter @Getter protected HopServerConfig serverConfig;
protected IVariables variables;
- protected boolean supportGraphicEnvironment;
+
+ @Setter @Getter protected boolean supportGraphicEnvironment;
@Setter @Getter private boolean jettyMode = false;
@@ -197,6 +201,68 @@ public class BaseHttpServlet extends HttpServlet {
}
}
+ /**
+ * Log server-side and return a {@link WebResult} error to the client in the requested XML or JSON
+ * shape. Use {@code writer} when the response writer is already acquired for this request;
+ * otherwise pass {@code null} and the output stream is used.
+ */
+ protected void writeXmlOrJsonApiError(
+ HttpServletResponse response,
+ PrintWriter writer,
+ boolean useXml,
+ boolean useJson,
+ String logMessage,
+ Throwable cause) {
+ log.logError(logMessage, cause);
+ final String clientMessage = "Unable to complete request.";
+ if (response.isCommitted()) {
+ return;
+ }
+ try {
+ response.resetBuffer();
+ } catch (IllegalStateException e) {
+ sendSafeError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, clientMessage);
+ return;
+ }
+ if (!useXml && !useJson) {
+ sendSafeError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, clientMessage);
+ return;
+ }
+ setResponseFormat(response, useXml, useJson);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ WebResult errorResult = new WebResult(WebResult.STRING_ERROR, clientMessage);
+ try {
+ if (useXml) {
+ String payload = XmlHandler.getXmlHeader(Const.XML_ENCODING) + errorResult.getXml();
+ byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
+ if (writer != null) {
+ writer.write(payload);
+ writer.flush();
+ } else {
+ OutputStream os = response.getOutputStream();
+ response.setContentLength(bytes.length);
+ os.write(bytes);
+ os.flush();
+ }
+ } else {
+ String payload = errorResult.getJson();
+ byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
+ if (writer != null) {
+ writer.write(payload);
+ writer.flush();
+ } else {
+ OutputStream os = response.getOutputStream();
+ response.setContentLength(bytes.length);
+ os.write(bytes);
+ os.flush();
+ }
+ }
+ } catch (IOException e) {
+ log.logError("Failed to write API error response", e);
+ sendSafeError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, clientMessage);
+ }
+ }
+
public PipelineMap getPipelineMap() {
if (pipelineMap == null) {
return HopServerSingleton.getInstance().getPipelineMap();
diff --git a/engine/src/main/java/org/apache/hop/www/BodyHttpServlet.java b/engine/src/main/java/org/apache/hop/www/BodyHttpServlet.java
index 0e54a97207..239f6690eb 100644
--- a/engine/src/main/java/org/apache/hop/www/BodyHttpServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/BodyHttpServlet.java
@@ -19,10 +19,11 @@ package org.apache.hop.www;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serial;
+import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.hop.core.Const;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.xml.XmlHandler;
@@ -53,7 +54,18 @@ public abstract class BodyHttpServlet extends BaseHttpServlet implements IHopSer
boolean useXML = useXML(request);
boolean useJson = isJsonRequest(request);
- PrintWriter out = new PrintWriter(response.getOutputStream());
+
+ final PrintWriter out;
+ try {
+ out =
+ new PrintWriter(
+ new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), true);
+ } catch (IOException e) {
+ logError("Failed to open servlet response stream", e);
+ sendSafeError(
+ response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process request.");
+ return;
+ }
try {
@@ -76,14 +88,15 @@ public abstract class BodyHttpServlet extends BaseHttpServlet implements IHopSer
}
} catch (Exception e) {
- String st = ExceptionUtils.getFullStackTrace(e);
+ logError("Servlet body generation failed", e);
+ final String clientMessage = "Request failed. See server log for details.";
if (useXML) {
- out.println(new WebResult(WebResult.STRING_ERROR, st).getXml());
+ out.println(new WebResult(WebResult.STRING_ERROR, clientMessage).getXml());
} else if (useJson) {
- out.println(new WebResult(WebResult.STRING_ERROR, st).getJson());
+ out.println(new WebResult(WebResult.STRING_ERROR, clientMessage).getJson());
} else {
out.println("<p><pre>");
- out.println(Encode.forHtml(st));
+ out.println(Encode.forHtml(clientMessage));
out.println("</pre>");
}
} finally {
diff --git a/engine/src/main/java/org/apache/hop/www/GetExecutionInfoServlet.java b/engine/src/main/java/org/apache/hop/www/GetExecutionInfoServlet.java
index 396e6733b9..013f662c88 100644
--- a/engine/src/main/java/org/apache/hop/www/GetExecutionInfoServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetExecutionInfoServlet.java
@@ -17,7 +17,6 @@
package org.apache.hop.www;
-import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.xml.bind.DataBindingException;
@@ -76,8 +75,7 @@ public class GetExecutionInfoServlet extends BaseHttpServlet implements IHopServ
}
@Override
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (isJettyMode() && !request.getContextPath().startsWith(CONTEXT_PATH)) {
return;
}
@@ -301,14 +299,18 @@ public class GetExecutionInfoServlet extends BaseHttpServlet implements IHopServ
iLocation.close();
}
} catch (Exception e) {
- String message = Const.getStackTracker(e);
+ logError("Execution info request failed", e);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ String clientMessage =
+ e instanceof HopException
+ ? Const.NVL(e.getMessage(), "Request failed.")
+ : "Unable to retrieve execution information.";
try {
- HopJson.newMapper().writeValue(out, message);
+ HopJson.newMapper().writeValue(out, clientMessage);
} catch (IOException | DataBindingException ex) {
- throw new ServletException(
- "Error writing execution state as JSON to servlet output stream", ex);
+ logError("Failed to write execution info error response as JSON", ex);
+ sendSafeError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, clientMessage);
}
- response.setStatus(500);
}
}
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 ccd1b8a12e..9e4f5cd4c6 100644
--- a/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetPipelineImageServlet.java
@@ -96,10 +96,18 @@ public class GetPipelineImageServlet extends BaseHttpServlet implements IHopServ
out.write(svgStream.toByteArray());
}
} catch (Exception e) {
- throw new IOException("Error building SVG image of pipleine", e);
+ logError("Error building SVG image of pipeline", e);
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Unable to generate pipeline image.");
} finally {
if (svgStream != null) {
- svgStream.close();
+ try {
+ svgStream.close();
+ } catch (IOException e) {
+ logError("Failed to close pipeline SVG buffer stream", e);
+ }
}
}
}
diff --git a/engine/src/main/java/org/apache/hop/www/GetPipelineStatusServlet.java b/engine/src/main/java/org/apache/hop/www/GetPipelineStatusServlet.java
index 61ffdddd11..8c605cd02b 100644
--- a/engine/src/main/java/org/apache/hop/www/GetPipelineStatusServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetPipelineStatusServlet.java
@@ -187,8 +187,14 @@ public class GetPipelineStatusServlet extends BaseHttpServlet implements IHopSer
}
out.flush();
response.flushBuffer();
- } catch (HopException e) {
- throw new ServletException("Unable to get the pipeline status in XML or JSON format", e);
+ } catch (HopException | IOException e) {
+ writeXmlOrJsonApiError(
+ response,
+ null,
+ useXml,
+ useJson,
+ "Unable to get the pipeline status in XML or JSON format",
+ e);
}
} else {
PrintWriter out = getSafeWriter(response);
@@ -561,8 +567,10 @@ public class GetPipelineStatusServlet extends BaseHttpServlet implements IHopSer
out.println(" pipelinelog.scrollTop=pipelinelog.scrollHeight; ");
out.println("</script> ");
} catch (Exception ex) {
+ logError("Error rendering pipeline status HTML page", ex);
out.println("<pre>");
- out.println(Encode.forHtml(Const.getStackTracker(ex)));
+ out.println(
+ Encode.forHtml("Unable to display pipeline status. See server log for details."));
out.println("</pre>");
}
diff --git a/engine/src/main/java/org/apache/hop/www/GetRootServlet.java b/engine/src/main/java/org/apache/hop/www/GetRootServlet.java
index 47e5d70c0c..dfdc00eace 100644
--- a/engine/src/main/java/org/apache/hop/www/GetRootServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetRootServlet.java
@@ -38,7 +38,7 @@ public class GetRootServlet extends BaseHttpServlet implements IHopServerPlugin
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (isJettyMode() && !request.getRequestURI().equals(CONTEXT_PATH)) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ sendSafeError(response, HttpServletResponse.SC_NOT_FOUND, "Not found.");
return;
}
diff --git a/engine/src/main/java/org/apache/hop/www/GetStatusServlet.java b/engine/src/main/java/org/apache/hop/www/GetStatusServlet.java
index d774013db6..f63870f037 100644
--- a/engine/src/main/java/org/apache/hop/www/GetStatusServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetStatusServlet.java
@@ -117,50 +117,53 @@ public class GetStatusServlet extends BaseHttpServlet implements IHopServerPlugi
List<HopServerObjectEntry> actions = getWorkflowMap().getWorkflowObjects();
if (useXml || useJson) {
- HopServerStatus serverStatus = new HopServerStatus();
- serverStatus.setStatusDescription("Online");
-
- getSystemInfo(serverStatus);
-
- for (HopServerObjectEntry entry : pipelineEntries) {
- IPipelineEngine<PipelineMeta> pipeline = getPipelineMap().getPipeline(entry);
- String statusDescription = pipeline.getStatusDescription();
-
- HopServerPipelineStatus pipelineStatus =
- new HopServerPipelineStatus(entry.getName(), entry.getId(), statusDescription);
- pipelineStatus.setLogDate(new Date());
- pipelineStatus.setExecutionStartDate(pipeline.getExecutionStartDate());
- pipelineStatus.setExecutionEndDate(pipeline.getExecutionEndDate());
- pipelineStatus.setPaused(pipeline.isPaused());
- serverStatus.getPipelineStatusList().add(pipelineStatus);
- }
+ try {
+ HopServerStatus serverStatus = new HopServerStatus();
+ serverStatus.setStatusDescription("Online");
- for (HopServerObjectEntry entry : actions) {
- IWorkflowEngine<WorkflowMeta> workflow = getWorkflowMap().getWorkflow(entry);
- String status = workflow.getStatusDescription();
- HopServerWorkflowStatus workflowStatus =
- new HopServerWorkflowStatus(entry.getName(), entry.getId(), status);
- workflowStatus.setLogDate(new Date());
- workflowStatus.setExecutionStartDate(workflow.getExecutionStartDate());
- workflowStatus.setExecutionEndDate(workflow.getExecutionEndDate());
- serverStatus.getWorkflowStatusList().add(workflowStatus);
- }
+ getSystemInfo(serverStatus);
+
+ for (HopServerObjectEntry entry : pipelineEntries) {
+ IPipelineEngine<PipelineMeta> pipeline = getPipelineMap().getPipeline(entry);
+ String statusDescription = pipeline.getStatusDescription();
+
+ HopServerPipelineStatus pipelineStatus =
+ new HopServerPipelineStatus(entry.getName(), entry.getId(), statusDescription);
+ pipelineStatus.setLogDate(new Date());
+ pipelineStatus.setExecutionStartDate(pipeline.getExecutionStartDate());
+ pipelineStatus.setExecutionEndDate(pipeline.getExecutionEndDate());
+ pipelineStatus.setPaused(pipeline.isPaused());
+ serverStatus.getPipelineStatusList().add(pipelineStatus);
+ }
+
+ for (HopServerObjectEntry entry : actions) {
+ IWorkflowEngine<WorkflowMeta> workflow = getWorkflowMap().getWorkflow(entry);
+ String status = workflow.getStatusDescription();
+ HopServerWorkflowStatus workflowStatus =
+ new HopServerWorkflowStatus(entry.getName(), entry.getId(), status);
+ workflowStatus.setLogDate(new Date());
+ workflowStatus.setExecutionStartDate(workflow.getExecutionStartDate());
+ workflowStatus.setExecutionEndDate(workflow.getExecutionEndDate());
+ serverStatus.getWorkflowStatusList().add(workflowStatus);
+ }
- if (useXml) {
- // XML
- try {
+ if (useXml) {
out.print(XmlHandler.getXmlHeader(Const.XML_ENCODING));
out.println(serverStatus.getXml());
- } catch (HopException e) {
- throw new ServletException("Unable to get the server status in XML format", e);
+ } else {
+ ObjectMapper mapper = HopJson.newMapper();
+ String jsonString =
+ mapper.writerWithDefaultPrettyPrinter().writeValueAsString(serverStatus);
+ out.println(jsonString);
}
- } else {
- // JSON
- //
- ObjectMapper mapper = HopJson.newMapper();
- String jsonString =
- mapper.writerWithDefaultPrettyPrinter().writeValueAsString(serverStatus);
- out.println(jsonString);
+ } catch (HopException | IOException e) {
+ writeXmlOrJsonApiError(
+ response,
+ out,
+ useXml,
+ useJson,
+ "Unable to get the server status in XML or JSON format",
+ e);
}
} else {
out.println("<HTML>");
@@ -651,8 +654,9 @@ public class GetStatusServlet extends BaseHttpServlet implements IHopServerPlugi
out.print(CONST_DIV); // end div
} catch (Exception ex) {
+ logError("Error rendering server status HTML page", ex);
out.println("<pre>");
- ex.printStackTrace(out);
+ out.println("Unable to display server status. See server log for details.");
out.println("</pre>");
}
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 a3ee599cec..61ec0463d5 100644
--- a/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetWorkflowImageServlet.java
@@ -80,8 +80,6 @@ public class GetWorkflowImageServlet extends BaseHttpServlet implements IHopServ
workflow = getWorkflowMap().getWorkflow(entry);
}
- ByteArrayOutputStream svgStream = null;
-
try {
if (workflow != null) {
@@ -94,23 +92,21 @@ public class GetWorkflowImageServlet extends BaseHttpServlet implements IHopServ
//
String svgXml =
WorkflowSvgPainter.generateWorkflowSvg(workflow.getWorkflowMeta(), 1.0f, variables);
- svgStream = new ByteArrayOutputStream();
- try {
+ try (ByteArrayOutputStream svgStream = new ByteArrayOutputStream()) {
svgStream.write(svgXml.getBytes(StandardCharsets.UTF_8));
- } finally {
svgStream.flush();
- }
- response.setContentLength(svgStream.size());
+ response.setContentLength(svgStream.size());
- OutputStream out = response.getOutputStream();
- out.write(svgStream.toByteArray());
+ OutputStream out = response.getOutputStream();
+ out.write(svgStream.toByteArray());
+ }
}
} catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (svgStream != null) {
- svgStream.close();
- }
+ logError("Error building SVG image of workflow", e);
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Unable to generate workflow image.");
}
}
diff --git a/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java b/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
index ec3a5c2985..472400c14d 100644
--- a/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
@@ -192,8 +192,14 @@ public class GetWorkflowStatusServlet extends BaseHttpServlet implements IHopSer
out.flush();
response.flushBuffer();
- } catch (HopException e) {
- throw new ServletException("Unable to get the workflow status in XML or JSON format", e);
+ } catch (HopException | IOException e) {
+ writeXmlOrJsonApiError(
+ response,
+ null,
+ useXml,
+ useJson,
+ "Unable to get the workflow status in XML or JSON format",
+ e);
}
} else {
diff --git a/engine/src/main/java/org/apache/hop/www/HopServerServlet.java b/engine/src/main/java/org/apache/hop/www/HopServerServlet.java
index ee44a0e646..447066338f 100644
--- a/engine/src/main/java/org/apache/hop/www/HopServerServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/HopServerServlet.java
@@ -87,15 +87,17 @@ public class HopServerServlet extends HttpServlet {
try {
plugin.doGet(req, resp);
} catch (ServletException e) {
- throw e;
+ log.logError("Hop server plugin request failed", e);
+ sendSafeError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Plugin request failed.");
} catch (Exception e) {
- throw new ServletException(e);
+ log.logError("Hop server plugin request failed", e);
+ sendSafeError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Plugin request failed.");
}
} else {
if (log.isDebug()) {
log.logDebug("Unable to find Hop Server Plugin for key: /hop" + req.getPathInfo());
}
- resp.sendError(404);
+ sendSafeError(resp, HttpServletResponse.SC_NOT_FOUND, "Not found.");
}
}
diff --git a/engine/src/main/java/org/apache/hop/www/HopServerSingleton.java b/engine/src/main/java/org/apache/hop/www/HopServerSingleton.java
index dfd754c057..27138d7943 100644
--- a/engine/src/main/java/org/apache/hop/www/HopServerSingleton.java
+++ b/engine/src/main/java/org/apache/hop/www/HopServerSingleton.java
@@ -136,7 +136,7 @@ public class HopServerSingleton {
Math.floor(
(System.currentTimeMillis()
- pipeline.getExecutionStartDate().getTime())
- / 60000);
+ / 60000.0);
if (diffInMinutes >= objectTimeout) {
// Let's remove this from the pipeline map...
//
@@ -178,7 +178,7 @@ public class HopServerSingleton {
Math.floor(
(System.currentTimeMillis()
- workflow.getExecutionStartDate().getTime())
- / 60000);
+ / 60000.0);
if (diffInMinutes >= objectTimeout) {
// Let's remove this from the workflow map...
//
diff --git a/engine/src/main/java/org/apache/hop/www/PipelineMap.java b/engine/src/main/java/org/apache/hop/www/PipelineMap.java
index 195af37475..3f8e7dbf4d 100644
--- a/engine/src/main/java/org/apache/hop/www/PipelineMap.java
+++ b/engine/src/main/java/org/apache/hop/www/PipelineMap.java
@@ -106,7 +106,11 @@ public class PipelineMap {
* @return the pipeline configuration with the specified entry
*/
public PipelineConfiguration getConfiguration(HopServerObjectEntry entry) {
- return pipelineMap.get(entry).getConfiguration();
+ PipelineData pipelineData = pipelineMap.get(entry);
+ if (pipelineData != null) {
+ return pipelineData.getConfiguration();
+ }
+ return null;
}
/**
diff --git a/engine/src/main/java/org/apache/hop/www/SniffTransformServlet.java b/engine/src/main/java/org/apache/hop/www/SniffTransformServlet.java
index 73f2e3ac46..0d208b3a39 100644
--- a/engine/src/main/java/org/apache/hop/www/SniffTransformServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/SniffTransformServlet.java
@@ -63,6 +63,7 @@ public class SniffTransformServlet extends BaseHttpServlet implements IHopServer
}
@Override
+ @SuppressWarnings("java:S2142")
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (isJettyMode() && !request.getContextPath().startsWith(CONTEXT_PATH)) {
@@ -200,7 +201,16 @@ public class SniffTransformServlet extends BaseHttpServlet implements IHopServer
response.setContentType("text/xml");
response.setCharacterEncoding(Const.XML_ENCODING);
out.print(XmlHandler.getXmlHeader(Const.XML_ENCODING));
- out.println(rowBuffer.getXml());
+ try {
+ out.println(rowBuffer.getXml());
+ } catch (IOException xmlEx) {
+ logError("Failed to serialize sniff rows to XML", xmlEx);
+ out.println(
+ new WebResult(
+ WebResult.STRING_ERROR,
+ "Failed to serialize rows to XML: " + xmlEx.getMessage())
+ .getXml());
+ }
} else if (useJson) {
diff --git a/engine/src/main/java/org/apache/hop/www/WebResult.java b/engine/src/main/java/org/apache/hop/www/WebResult.java
index 362295041e..f872f77759 100644
--- a/engine/src/main/java/org/apache/hop/www/WebResult.java
+++ b/engine/src/main/java/org/apache/hop/www/WebResult.java
@@ -17,6 +17,7 @@
package org.apache.hop.www;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.hop.core.Const;
import org.apache.hop.core.exception.HopXmlException;
@@ -68,6 +69,7 @@ public class WebResult {
return xml.toString();
}
+ @JsonIgnore
public String getJson() {
try {
return HopJson.newMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
diff --git a/engine/src/main/java/org/apache/hop/www/WebServiceServlet.java b/engine/src/main/java/org/apache/hop/www/WebServiceServlet.java
index 4807278f0f..242bfff3f5 100644
--- a/engine/src/main/java/org/apache/hop/www/WebServiceServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/WebServiceServlet.java
@@ -101,8 +101,11 @@ public class WebServiceServlet extends BaseHttpServlet implements IHopServerPlug
String webServiceName = request.getParameter("service");
if (StringUtils.isEmpty(webServiceName)) {
- throw new ServletException(
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_BAD_REQUEST,
"Please specify a service parameter pointing to the name of the web service object");
+ return;
}
String runConfigurationName = request.getParameter("runConfig");
@@ -282,7 +285,11 @@ public class WebServiceServlet extends BaseHttpServlet implements IHopServerPlug
pipeline.waitUntilFinished();
} catch (Exception e) {
- throw new ServletException("Error producing web service output", e);
+ logError("Error producing web service output", e);
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Error producing web service output.");
}
}
diff --git a/engine/src/main/java/org/apache/hop/www/jaxrs/HopServerResource.java b/engine/src/main/java/org/apache/hop/www/jaxrs/HopServerResource.java
index 3244dfab46..647c81668e 100644
--- a/engine/src/main/java/org/apache/hop/www/jaxrs/HopServerResource.java
+++ b/engine/src/main/java/org/apache/hop/www/jaxrs/HopServerResource.java
@@ -31,6 +31,10 @@ import org.apache.hop.www.HopServerConfig;
import org.apache.hop.www.HopServerObjectEntry;
import org.apache.hop.www.HopServerSingleton;
+/**
+ * @deprecated Since 2.18.0
+ */
+@Deprecated(since = "2.18.0")
@Path("/carte")
public class HopServerResource {
diff --git a/engine/src/main/java/org/apache/hop/www/jaxrs/PipelineResource.java b/engine/src/main/java/org/apache/hop/www/jaxrs/PipelineResource.java
index d5a1d0f1ec..dc378da9d8 100644
--- a/engine/src/main/java/org/apache/hop/www/jaxrs/PipelineResource.java
+++ b/engine/src/main/java/org/apache/hop/www/jaxrs/PipelineResource.java
@@ -45,6 +45,10 @@ import org.apache.hop.pipeline.transform.TransformStatus;
import org.apache.hop.www.HopServerObjectEntry;
import org.apache.hop.www.HopServerSingleton;
+/**
+ * @deprecated Since 2.18.0
+ */
+@Deprecated(since = "2.18.0")
@Path("/carte/pipeline")
public class PipelineResource {
diff --git a/engine/src/main/java/org/apache/hop/www/jaxrs/WorkflowResource.java b/engine/src/main/java/org/apache/hop/www/jaxrs/WorkflowResource.java
index fd722b5ef9..0cc185b0b2 100644
--- a/engine/src/main/java/org/apache/hop/www/jaxrs/WorkflowResource.java
+++ b/engine/src/main/java/org/apache/hop/www/jaxrs/WorkflowResource.java
@@ -41,6 +41,10 @@ import org.apache.hop.workflow.engine.WorkflowEngineFactory;
import org.apache.hop.www.HopServerObjectEntry;
import org.apache.hop.www.HopServerSingleton;
+/**
+ * @deprecated Since 2.18.0
+ */
+@Deprecated(since = "2.18.0")
@Path("/carte/workflow")
public class WorkflowResource {
diff --git a/engine/src/test/java/org/apache/hop/www/BaseHttpServletTest.java b/engine/src/test/java/org/apache/hop/www/BaseHttpServletTest.java
new file mode 100644
index 0000000000..9f33f568d7
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/BaseHttpServletTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.www;
+
+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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import org.apache.hop.core.logging.ILogChannel;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class BaseHttpServletTest {
+
+ private BaseHttpServlet servlet;
+
+ @BeforeEach
+ void setUp() {
+ servlet = new BaseHttpServlet();
+ servlet.setLog(mock(ILogChannel.class));
+ }
+
+ @Test
+ void supportGraphicEnvironmentGetterSetter() {
+ assertFalse(servlet.isSupportGraphicEnvironment());
+ servlet.setSupportGraphicEnvironment(true);
+ assertTrue(servlet.isSupportGraphicEnvironment());
+ }
+
+ @Test
+ void convertContextPathJettyVsNonJetty() {
+ servlet.setJettyMode(true);
+ assertEquals("/hop/status", servlet.convertContextPath("/hop/status"));
+ servlet.setJettyMode(false);
+ assertEquals("status", servlet.convertContextPath("/hop/status"));
+ }
+
+ @Test
+ void isJsonRequestDetectsParameter() {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getParameter("json")).thenReturn("Y");
+ assertTrue(servlet.isJsonRequest(request));
+ when(request.getParameter("json")).thenReturn("n");
+ assertFalse(servlet.isJsonRequest(request));
+ }
+
+ @Test
+ void sendSafeErrorWhenCommittedOnlySetsStatus() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.isCommitted()).thenReturn(true);
+ servlet.sendSafeError(response, HttpServletResponse.SC_BAD_REQUEST, "bad");
+ verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ verify(response, org.mockito.Mockito.never()).sendError(anyInt(), anyString());
+ }
+
+ @Test
+ void sendSafeErrorFallsBackToSetStatusWhenSendErrorThrows() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.isCommitted()).thenReturn(false);
+ doThrow(new IOException("broken")).when(response).sendError(anyInt(), anyString());
+ servlet.sendSafeError(response, HttpServletResponse.SC_CONFLICT, "msg");
+ verify(response).setStatus(HttpServletResponse.SC_CONFLICT);
+ }
+
+ @Test
+ void writeXmlOrJsonApiErrorWritesXmlToOutputStream() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.isCommitted()).thenReturn(false);
+ doNothing().when(response).resetBuffer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ServletOutputStream sos =
+ new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {}
+
+ @Override
+ public void write(int b) throws IOException {
+ baos.write(b);
+ }
+ };
+ when(response.getOutputStream()).thenReturn(sos);
+
+ servlet.writeXmlOrJsonApiError(
+ response, null, true, false, "unit test", new IllegalStateException("cause"));
+
+ verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ String body = baos.toString(StandardCharsets.UTF_8);
+ assertTrue(body.contains(WebResult.STRING_ERROR));
+ assertTrue(body.contains("Unable to complete request."));
+ }
+
+ @Test
+ void writeXmlOrJsonApiErrorUsesWriterWhenProvided() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.isCommitted()).thenReturn(false);
+ doNothing().when(response).resetBuffer();
+ StringWriter sw = new StringWriter();
+ PrintWriter writer = new PrintWriter(sw);
+
+ servlet.writeXmlOrJsonApiError(
+ response, writer, false, true, "unit test json", new RuntimeException("x"));
+ writer.flush();
+
+ verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ assertTrue(sw.toString().contains(WebResult.STRING_ERROR));
+ }
+
+ @Test
+ void getSafeWriterReturnsNullAndSendsErrorWhenGetWriterThrows() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.isCommitted()).thenReturn(false);
+ when(response.getWriter()).thenThrow(new IOException("no writer"));
+ doNothing()
+ .when(response)
+ .sendError(eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), anyString());
+
+ assertNull(servlet.getSafeWriter(response));
+ verify(response).sendError(eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), anyString());
+ }
+
+ @Test
+ void getSafeWriterReturnsWriter() throws IOException {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ PrintWriter pw = new PrintWriter(new StringWriter());
+ when(response.getWriter()).thenReturn(pw);
+ assertNotNull(servlet.getSafeWriter(response));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/BodyHttpServletTest.java b/engine/src/test/java/org/apache/hop/www/BodyHttpServletTest.java
new file mode 100644
index 0000000000..23b81262f7
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/BodyHttpServletTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.variables.IVariables;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class BodyHttpServletTest {
+
+ @BeforeEach
+ void initLog() {
+ HopLogStore.init();
+ }
+
+ static final class OkBodyServlet extends BodyHttpServlet {
+ @Override
+ WebResult generateBody(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ boolean useXML,
+ IVariables variables) {
+ return WebResult.OK;
+ }
+
+ @Override
+ public String getContextPath() {
+ return "/hop/okBody";
+ }
+ }
+
+ static final class FailingBodyServlet extends BodyHttpServlet {
+ @Override
+ WebResult generateBody(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ boolean useXML,
+ IVariables variables) {
+ throw new RuntimeException("boom");
+ }
+
+ @Override
+ public String getContextPath() {
+ return "/hop/failBody";
+ }
+ }
+
+ @Test
+ void doGetReturnsJsonWhenRequested() throws IOException {
+ OkBodyServlet servlet = new OkBodyServlet();
+ servlet.setJettyMode(true);
+ servlet.setLog(org.mockito.Mockito.mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn("/hop/okBody");
+ when(request.getParameter("xml")).thenReturn(null);
+ when(request.getParameter("json")).thenReturn("Y");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ServletOutputStream sos =
+ new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {}
+
+ @Override
+ public void write(int b) throws IOException {
+ baos.write(b);
+ }
+ };
+
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getOutputStream()).thenReturn(sos);
+
+ servlet.doGet(request, response);
+
+ String body = baos.toString(StandardCharsets.UTF_8);
+ assertTrue(body.contains(WebResult.STRING_OK));
+ }
+
+ @Test
+ void doGetReturnsErrorJsonWhenGenerateBodyFails() throws IOException {
+ FailingBodyServlet servlet = new FailingBodyServlet();
+ servlet.setJettyMode(true);
+ servlet.setLog(org.mockito.Mockito.mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn("/hop/failBody");
+ when(request.getParameter("json")).thenReturn("Y");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ServletOutputStream sos =
+ new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {}
+
+ @Override
+ public void write(int b) throws IOException {
+ baos.write(b);
+ }
+ };
+
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getOutputStream()).thenReturn(sos);
+
+ servlet.doGet(request, response);
+
+ assertTrue(baos.toString(StandardCharsets.UTF_8).contains(WebResult.STRING_ERROR));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/DefaultWebServerShutdownHandlerTest.java
similarity index 51%
copy from engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
copy to engine/src/test/java/org/apache/hop/www/DefaultWebServerShutdownHandlerTest.java
index e1ad781c64..0788814d57 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/DefaultWebServerShutdownHandlerTest.java
@@ -17,31 +17,12 @@
package org.apache.hop.www;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.junit.jupiter.api.Test;
-/**
- * Tests for GetRootServlet class
- *
- * @see GetRootServlet
- */
-class GetRootServletTest {
+class DefaultWebServerShutdownHandlerTest {
@Test
- void testDoGetReturn404StatusCode() throws ServletException, IOException {
- GetRootServlet servlet = new GetRootServlet();
- servlet.setJettyMode(true);
- HttpServletRequest request =
- when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
- HttpServletResponse response = mock(HttpServletResponse.class);
- servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ void shutdownWebServerIsNoOp() throws Exception {
+ new DefaultWebServerShutdownHandler().shutdownWebServer();
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/GetExecutionInfoServletTypeTest.java
similarity index 51%
copy from engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
copy to engine/src/test/java/org/apache/hop/www/GetExecutionInfoServletTypeTest.java
index e1ad781c64..21a58dac3e 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/GetExecutionInfoServletTypeTest.java
@@ -17,31 +17,18 @@
package org.apache.hop.www;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.junit.jupiter.api.Test;
-/**
- * Tests for GetRootServlet class
- *
- * @see GetRootServlet
- */
-class GetRootServletTest {
+class GetExecutionInfoServletTypeTest {
@Test
- void testDoGetReturn404StatusCode() throws ServletException, IOException {
- GetRootServlet servlet = new GetRootServlet();
- servlet.setJettyMode(true);
- HttpServletRequest request =
- when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
- HttpServletResponse response = mock(HttpServletResponse.class);
- servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ void typeEnumValuesAreStable() {
+ GetExecutionInfoServlet.Type[] values = GetExecutionInfoServlet.Type.values();
+ assertEquals(10, values.length);
+ assertSame(GetExecutionInfoServlet.Type.STATE, GetExecutionInfoServlet.Type.valueOf("STATE"));
+ assertSame(GetExecutionInfoServlet.Type.DELETE, GetExecutionInfoServlet.Type.valueOf("DELETE"));
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/GetPipelineImageServletTest.java b/engine/src/test/java/org/apache/hop/www/GetPipelineImageServletTest.java
new file mode 100644
index 0000000000..f4efaea99d
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/GetPipelineImageServletTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.www;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import org.apache.hop.core.logging.HopLogStore;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class GetPipelineImageServletTest {
+
+ @BeforeEach
+ void initLog() {
+ HopLogStore.init();
+ }
+
+ @Test
+ void doGetReturnsEarlyWhenContextPathDoesNotMatchInJettyMode()
+ throws IOException, ServletException {
+ GetPipelineImageServlet servlet = new GetPipelineImageServlet();
+ servlet.setJettyMode(true);
+ servlet.setLog(mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn("/wrong");
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ servlet.doGet(request, response);
+
+ verifyNoInteractions(response);
+ }
+
+ @Test
+ void doGetReturnsNoContentWhenGraphicsDisabled() throws IOException, ServletException {
+ GetPipelineImageServlet servlet = new GetPipelineImageServlet();
+ servlet.setJettyMode(true);
+ servlet.setSupportGraphicEnvironment(false);
+ servlet.setLog(mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn(GetPipelineImageServlet.CONTEXT_PATH);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ servlet.doGet(request, response);
+
+ verify(response).setStatus(eq(HttpServletResponse.SC_NO_CONTENT));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
index e1ad781c64..3e9c5c5995 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
@@ -17,6 +17,8 @@
package org.apache.hop.www;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -25,6 +27,8 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import org.junit.jupiter.api.Test;
/**
@@ -34,6 +38,22 @@ import org.junit.jupiter.api.Test;
*/
class GetRootServletTest {
+ @Test
+ void testDoGetWritesHtmlForRootUri() throws ServletException, IOException {
+ GetRootServlet servlet = new GetRootServlet();
+ servlet.setJettyMode(true);
+ StringWriter body = new StringWriter();
+ HttpServletRequest request =
+ when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/").getMock();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getWriter()).thenReturn(new PrintWriter(body));
+
+ servlet.doGet(request, response);
+
+ verify(response).setStatus(HttpServletResponse.SC_OK);
+ assertTrue(body.toString().contains("<HTML>"));
+ }
+
@Test
void testDoGetReturn404StatusCode() throws ServletException, IOException {
GetRootServlet servlet = new GetRootServlet();
@@ -42,6 +62,6 @@ class GetRootServletTest {
when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
HttpServletResponse response = mock(HttpServletResponse.class);
servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), eq("Not found."));
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/GetWorkflowImageServletTest.java b/engine/src/test/java/org/apache/hop/www/GetWorkflowImageServletTest.java
new file mode 100644
index 0000000000..c4af0d13c0
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/GetWorkflowImageServletTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.www;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import org.apache.hop.core.logging.HopLogStore;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class GetWorkflowImageServletTest {
+
+ @BeforeEach
+ void initLog() {
+ HopLogStore.init();
+ }
+
+ @Test
+ void doGetReturnsEarlyWhenContextPathDoesNotMatchInJettyMode()
+ throws IOException, ServletException {
+ GetWorkflowImageServlet servlet = new GetWorkflowImageServlet();
+ servlet.setJettyMode(true);
+ servlet.setLog(mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn("/wrong");
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ servlet.doGet(request, response);
+
+ verifyNoInteractions(response);
+ }
+
+ @Test
+ void doGetReturnsNoContentWhenGraphicsDisabled() throws IOException, ServletException {
+ GetWorkflowImageServlet servlet = new GetWorkflowImageServlet();
+ servlet.setJettyMode(true);
+ servlet.setSupportGraphicEnvironment(false);
+ servlet.setLog(mock(org.apache.hop.core.logging.ILogChannel.class));
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getContextPath()).thenReturn(GetWorkflowImageServlet.CONTEXT_PATH);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+
+ servlet.doGet(request, response);
+
+ verify(response).setStatus(eq(HttpServletResponse.SC_NO_CONTENT));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/HopServerCommandExceptionTest.java
similarity index 51%
copy from engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
copy to engine/src/test/java/org/apache/hop/www/HopServerCommandExceptionTest.java
index e1ad781c64..4655de5936 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/HopServerCommandExceptionTest.java
@@ -17,31 +17,24 @@
package org.apache.hop.www;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.junit.jupiter.api.Test;
-/**
- * Tests for GetRootServlet class
- *
- * @see GetRootServlet
- */
-class GetRootServletTest {
+class HopServerCommandExceptionTest {
+
+ @Test
+ void messageConstructor() {
+ HopServer.HopServerCommandException ex = new HopServer.HopServerCommandException("m1");
+ assertEquals("m1", ex.getMessage());
+ }
@Test
- void testDoGetReturn404StatusCode() throws ServletException, IOException {
- GetRootServlet servlet = new GetRootServlet();
- servlet.setJettyMode(true);
- HttpServletRequest request =
- when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
- HttpServletResponse response = mock(HttpServletResponse.class);
- servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ void messageAndCauseConstructor() {
+ Throwable cause = new IllegalStateException("root");
+ HopServer.HopServerCommandException ex = new HopServer.HopServerCommandException("wrap", cause);
+ assertEquals("wrap", ex.getMessage());
+ assertSame(cause, ex.getCause());
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/HopServerObjectEntryTest.java b/engine/src/test/java/org/apache/hop/www/HopServerObjectEntryTest.java
new file mode 100644
index 0000000000..b074264209
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/HopServerObjectEntryTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class HopServerObjectEntryTest {
+
+ @Test
+ void equalsUsesIdOnly() {
+ HopServerObjectEntry a = new HopServerObjectEntry("n1", "id-a");
+ HopServerObjectEntry b = new HopServerObjectEntry("n2", "id-a");
+ HopServerObjectEntry c = new HopServerObjectEntry("n1", "id-b");
+ assertEquals(a, b);
+ assertEquals(a, a);
+ assertNotEquals(a, c);
+ assertFalse(a.equals("x"));
+ }
+
+ @Test
+ void hashCodeUsesId() {
+ assertEquals(
+ new HopServerObjectEntry("a", "same").hashCode(),
+ new HopServerObjectEntry("b", "same").hashCode());
+ }
+
+ @Test
+ void compareOrdersByNameThenId() {
+ HopServerObjectEntry x = new HopServerObjectEntry("b", "2");
+ HopServerObjectEntry y = new HopServerObjectEntry("a", "9");
+ assertTrue(x.compareTo(y) > 0);
+ HopServerObjectEntry z = new HopServerObjectEntry("b", "1");
+ assertTrue(x.compareTo(z) > 0);
+ }
+
+ @Test
+ void defaultConstructorAndSetters() {
+ HopServerObjectEntry e = new HopServerObjectEntry();
+ e.setName("nm");
+ e.setId("i1");
+ assertEquals("nm", e.getName());
+ assertEquals("i1", e.getId());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/HopServerPluginServletSmokeTest.java b/engine/src/test/java/org/apache/hop/www/HopServerPluginServletSmokeTest.java
new file mode 100644
index 0000000000..ecdad5d8a7
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/HopServerPluginServletSmokeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Light coverage for servlet identity methods (context path, service description) on plugins that
+ * are expensive to exercise end-to-end.
+ */
+class HopServerPluginServletSmokeTest {
+
+ private static void assertPluginPaths(IHopServerPlugin servlet, String expectedContextPath) {
+ assertEquals(expectedContextPath, servlet.getContextPath());
+ assertTrue(servlet.getService().startsWith(expectedContextPath));
+ }
+
+ @Test
+ void registerWorkflowServletPaths() {
+ assertPluginPaths(new RegisterWorkflowServlet(), RegisterWorkflowServlet.CONTEXT_PATH);
+ }
+
+ @Test
+ void deleteExecutionInfoServletPaths() {
+ assertPluginPaths(new DeleteExecutionInfoServlet(), DeleteExecutionInfoServlet.CONTEXT_PATH);
+ }
+
+ @Test
+ void execPipelineServletPaths() {
+ assertPluginPaths(new ExecPipelineServlet(), ExecPipelineServlet.CONTEXT_PATH);
+ }
+
+ @Test
+ void execWorkflowServletPaths() {
+ assertPluginPaths(new ExecWorkflowServlet(), ExecWorkflowServlet.CONTEXT_PATH);
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/HopServerServletTest.java b/engine/src/test/java/org/apache/hop/www/HopServerServletTest.java
new file mode 100644
index 0000000000..076e6e3d21
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/HopServerServletTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.www;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hop.core.logging.HopLogStore;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class HopServerServletTest {
+
+ private HopServerServlet servlet;
+ private Map<String, IHopServerPlugin> registry;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ HopLogStore.init();
+ servlet = new HopServerServlet();
+ registry = new ConcurrentHashMap<>();
+ Field f = HopServerServlet.class.getDeclaredField("hopServerPluginRegistry");
+ f.setAccessible(true);
+ f.set(servlet, registry);
+ }
+
+ @Test
+ void doGetDispatchesToRegisteredPlugin() throws Exception {
+ IHopServerPlugin plugin = mock(IHopServerPlugin.class);
+ registry.put("/myPlugin", plugin);
+
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getPathInfo()).thenReturn("/myPlugin");
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+
+ servlet.doGet(req, resp);
+
+ verify(plugin).doGet(req, resp);
+ }
+
+ @Test
+ void doGetSendsNotFoundWhenPluginMissing() throws Exception {
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getPathInfo()).thenReturn("/unknown");
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+ when(resp.isCommitted()).thenReturn(false);
+
+ servlet.doGet(req, resp);
+
+ verify(resp).sendError(eq(HttpServletResponse.SC_NOT_FOUND), eq("Not found."));
+ }
+
+ @Test
+ void doGetTrimsTrailingSlashOnPath() throws Exception {
+ IHopServerPlugin plugin = mock(IHopServerPlugin.class);
+ registry.put("/trim", plugin);
+
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getPathInfo()).thenReturn("/trim/");
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+
+ servlet.doGet(req, resp);
+
+ verify(plugin).doGet(req, resp);
+ }
+
+ @Test
+ void doGetHandlesPluginServletException() throws Exception {
+ IHopServerPlugin plugin = mock(IHopServerPlugin.class);
+ doThrow(new ServletException("plugin failed")).when(plugin).doGet(any(), any());
+ registry.put("/bad", plugin);
+
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getPathInfo()).thenReturn("/bad");
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+ when(resp.isCommitted()).thenReturn(false);
+
+ servlet.doGet(req, resp);
+
+ verify(resp)
+ .sendError(eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), eq("Plugin request failed."));
+ }
+
+ @Test
+ void doPostCatchesFailureWhenRegistryUninitialized() throws Exception {
+ HopServerServlet bare = new HopServerServlet();
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getPathInfo()).thenReturn("/any");
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+ when(resp.isCommitted()).thenReturn(false);
+
+ bare.doPost(req, resp);
+
+ verify(resp)
+ .sendError(
+ eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR),
+ eq("Unable to process server request."));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/HopServerSingletonTest.java b/engine/src/test/java/org/apache/hop/www/HopServerSingletonTest.java
new file mode 100644
index 0000000000..36fd9a8f48
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/HopServerSingletonTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.www;
+
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.hop.core.Const;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class HopServerSingletonTest {
+
+ @Test
+ void installPurgeTimerDoesNothingWhenTimeoutIsZeroViaEnvironment() {
+ HopServerConfig config = mock(HopServerConfig.class);
+ when(config.getObjectTimeoutMinutes()).thenReturn(0);
+ System.setProperty(Const.HOP_SERVER_OBJECT_TIMEOUT_MINUTES, "0");
+
+ ILogChannel log = mock(ILogChannel.class);
+ HopServerSingleton.installPurgeTimer(config, log, new PipelineMap(), new WorkflowMap());
+
+ verify(log, never()).logBasic(contains("Installing timer"));
+ }
+
+ @Test
+ void installPurgeTimerLogsWhenTimeoutPositive() {
+ HopServerConfig config = mock(HopServerConfig.class);
+ when(config.getObjectTimeoutMinutes()).thenReturn(15);
+ System.clearProperty(Const.HOP_SERVER_OBJECT_TIMEOUT_MINUTES);
+
+ ILogChannel log = mock(ILogChannel.class);
+ HopServerSingleton.installPurgeTimer(config, log, new PipelineMap(), new WorkflowMap());
+
+ verify(log).logBasic(contains("Installing timer"));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/HopServerStatusTest.java b/engine/src/test/java/org/apache/hop/www/HopServerStatusTest.java
new file mode 100644
index 0000000000..081c0e63b0
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/HopServerStatusTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.www;
+
+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 org.apache.hop.core.exception.HopException;
+import org.junit.jupiter.api.Test;
+
+class HopServerStatusTest {
+
+ @Test
+ void defaultConstructorInitializesLists() {
+ HopServerStatus status = new HopServerStatus();
+ assertNotNull(status.getPipelineStatusList());
+ assertNotNull(status.getWorkflowStatusList());
+ assertTrue(status.getPipelineStatusList().isEmpty());
+ }
+
+ @Test
+ void statusDescriptionConstructor() {
+ HopServerStatus status = new HopServerStatus("Online");
+ assertEquals("Online", status.getStatusDescription());
+ }
+
+ @Test
+ void getXmlWithEmptyLists() throws HopException {
+ HopServerStatus status = new HopServerStatus("Up");
+ status.setMemoryFree(1L);
+ status.setMemoryTotal(2L);
+ status.setCpuCores(4);
+ String xml = status.getXml();
+ assertTrue(xml.contains(HopServerStatus.XML_TAG));
+ assertTrue(xml.contains("Up"));
+ assertTrue(xml.contains("pipeline_status_list"));
+ assertTrue(xml.contains("workflow_status_list"));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/IHopServerServletTest.java
similarity index 51%
copy from engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
copy to engine/src/test/java/org/apache/hop/www/IHopServerServletTest.java
index e1ad781c64..b3f41a8043 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/IHopServerServletTest.java
@@ -17,31 +17,15 @@
package org.apache.hop.www;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertEquals;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.junit.jupiter.api.Test;
-/**
- * Tests for GetRootServlet class
- *
- * @see GetRootServlet
- */
-class GetRootServletTest {
+class IHopServerServletTest {
@Test
- void testDoGetReturn404StatusCode() throws ServletException, IOException {
- GetRootServlet servlet = new GetRootServlet();
- servlet.setJettyMode(true);
- HttpServletRequest request =
- when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
- HttpServletResponse response = mock(HttpServletResponse.class);
- servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ void getServiceImplementedByLambda() {
+ IHopServerServlet servlet = () -> "svc";
+ assertEquals("svc", servlet.getService());
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/PipelineMapTest.java b/engine/src/test/java/org/apache/hop/www/PipelineMapTest.java
new file mode 100644
index 0000000000..e115562f46
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/PipelineMapTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import org.apache.hop.pipeline.PipelineConfiguration;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engine.IPipelineEngine;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PipelineMapTest {
+
+ private PipelineMap map;
+
+ @BeforeEach
+ void setUp() {
+ map = new PipelineMap();
+ }
+
+ @Test
+ void addGetRemoveAndList() {
+ IPipelineEngine<PipelineMeta> pipeline = mock(IPipelineEngine.class);
+ PipelineMeta meta = mock(PipelineMeta.class);
+ when(pipeline.getPipelineMeta()).thenReturn(meta);
+ when(meta.getName()).thenReturn("P1");
+ PipelineConfiguration configuration = mock(PipelineConfiguration.class);
+
+ map.addPipeline("P1", "cid-1", pipeline, configuration);
+
+ HopServerObjectEntry entry = new HopServerObjectEntry("P1", "cid-1");
+ assertSame(pipeline, map.getPipeline(entry));
+ assertSame(pipeline, map.getPipeline("P1"));
+ assertSame(configuration, map.getConfiguration(entry));
+ assertSame(configuration, map.getConfiguration("P1"));
+
+ List<HopServerObjectEntry> objects = map.getPipelineObjects();
+ assertEquals(1, objects.size());
+ assertEquals(entry.getId(), objects.get(0).getId());
+
+ assertNotNull(map.getFirstServerObjectEntry("P1"));
+
+ map.removePipeline(entry);
+ assertNull(map.getPipeline(entry));
+ assertTrue(map.getPipelineObjects().isEmpty());
+ }
+
+ @Test
+ void getConfigurationReturnsNullForUnknownEntry() {
+ assertNull(map.getConfiguration(new HopServerObjectEntry("x", "y")));
+ }
+
+ @Test
+ void hopServerConfigRoundTrip() {
+ HopServerConfig cfg = mock(HopServerConfig.class);
+ map.setHopServerConfig(cfg);
+ assertSame(cfg, map.getHopServerConfig());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/SocketPortAllocationTest.java b/engine/src/test/java/org/apache/hop/www/SocketPortAllocationTest.java
new file mode 100644
index 0000000000..8ce3c571e9
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/SocketPortAllocationTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Date;
+import org.junit.jupiter.api.Test;
+
+class SocketPortAllocationTest {
+
+ @Test
+ void constructorAndAccessors() {
+ Date now = new Date();
+ SocketPortAllocation a =
+ new SocketPortAllocation(
+ 8080, now, "cluster1", "pipe", "srcSrv", "srcTr", "0", "tgtSrv", "tgtTr", "1");
+ assertEquals(8080, a.getPort());
+ assertEquals(now, a.getLastRequested());
+ assertEquals("cluster1", a.getClusterRunId());
+ assertEquals("pipe", a.getPipelineName());
+ assertTrue(a.isAllocated());
+ a.setAllocated(false);
+ assertFalse(a.isAllocated());
+ a.setPort(9090);
+ assertEquals(9090, a.getPort());
+ a.setPipelineName("p2");
+ assertEquals("p2", a.getPipelineName());
+ }
+
+ @Test
+ void equalsAndHashCodeUsePort() {
+ Date d = new Date();
+ SocketPortAllocation x =
+ new SocketPortAllocation(1, d, "c", "p", "s", "t", "0", "s2", "t2", "1");
+ SocketPortAllocation y =
+ new SocketPortAllocation(1, d, "other", "p2", "s", "t", "0", "s2", "t2", "1");
+ assertEquals(x, y);
+ SocketPortAllocation z =
+ new SocketPortAllocation(2, d, "c", "p", "s", "t", "0", "s2", "t2", "1");
+ assertNotEquals(x, z);
+ assertEquals(Integer.valueOf(1).hashCode(), x.hashCode());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/SslConfigurationTest.java b/engine/src/test/java/org/apache/hop/www/SslConfigurationTest.java
new file mode 100644
index 0000000000..4b0adc6695
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/SslConfigurationTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class SslConfigurationTest {
+
+ @Test
+ void defaultConstructorSetsKeyStoreType() {
+ SslConfiguration ssl = new SslConfiguration();
+ assertNotNull(ssl.getKeyStoreType());
+ }
+
+ @Test
+ void setKeyStoreTypeIgnoresNull() {
+ SslConfiguration ssl = new SslConfiguration();
+ String before = ssl.getKeyStoreType();
+ ssl.setKeyStoreType(null);
+ assertEquals(before, ssl.getKeyStoreType());
+ ssl.setKeyStoreType("PKCS12");
+ assertEquals("PKCS12", ssl.getKeyStoreType());
+ }
+
+ @Test
+ void setKeyStoreRequiresNonEmpty() {
+ SslConfiguration ssl = new SslConfiguration();
+ assertThrows(IllegalArgumentException.class, () -> ssl.setKeyStore(null));
+ assertThrows(IllegalArgumentException.class, () -> ssl.setKeyStore(""));
+ ssl.setKeyStore("/path/ks.p12");
+ assertEquals("/path/ks.p12", ssl.getKeyStore());
+ }
+
+ @Test
+ void setKeyStorePasswordRequiresNonEmpty() {
+ SslConfiguration ssl = new SslConfiguration();
+ ssl.setKeyStore("/ks");
+ assertThrows(IllegalArgumentException.class, () -> ssl.setKeyStorePassword(null));
+ assertThrows(IllegalArgumentException.class, () -> ssl.setKeyStorePassword(""));
+ ssl.setKeyStorePassword("secret");
+ assertEquals("secret", ssl.getKeyStorePassword());
+ }
+
+ @Test
+ void getKeyPasswordFallsBackToKeyStorePassword() {
+ SslConfiguration ssl = new SslConfiguration();
+ ssl.setKeyStore("/ks");
+ ssl.setKeyStorePassword("storePass");
+ assertEquals("storePass", ssl.getKeyPassword());
+ ssl.setKeyPassword("keyPass");
+ assertEquals("keyPass", ssl.getKeyPassword());
+ }
+
+ @Test
+ void getXmlIncludesTagWhenValuesSet() {
+ SslConfiguration ssl = new SslConfiguration();
+ ssl.setKeyStore("/tmp/test.jks");
+ ssl.setKeyStorePassword("p1");
+ String xml = ssl.getXml();
+ assertTrue(xml.contains(SslConfiguration.XML_TAG));
+ assertTrue(xml.contains("/tmp/test.jks"));
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java b/engine/src/test/java/org/apache/hop/www/StatusServletUtilsTest.java
similarity index 51%
copy from engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
copy to engine/src/test/java/org/apache/hop/www/StatusServletUtilsTest.java
index e1ad781c64..8b0ca7e36f 100644
--- a/engine/src/test/java/org/apache/hop/www/GetRootServletTest.java
+++ b/engine/src/test/java/org/apache/hop/www/StatusServletUtilsTest.java
@@ -17,31 +17,16 @@
package org.apache.hop.www;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertEquals;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
import org.junit.jupiter.api.Test;
-/**
- * Tests for GetRootServlet class
- *
- * @see GetRootServlet
- */
-class GetRootServletTest {
+class StatusServletUtilsTest {
@Test
- void testDoGetReturn404StatusCode() throws ServletException, IOException {
- GetRootServlet servlet = new GetRootServlet();
- servlet.setJettyMode(true);
- HttpServletRequest request =
- when(mock(HttpServletRequest.class).getRequestURI()).thenReturn("/wrong_path").getMock();
- HttpServletResponse response = mock(HttpServletResponse.class);
- servlet.doGet(request, response);
- verify(response).sendError(HttpServletResponse.SC_NOT_FOUND);
+ void constantsAreStable() {
+ assertEquals("/content/common-ui/resources/themes", StatusServletUtils.RESOURCES_PATH);
+ assertEquals("/static", StatusServletUtils.STATIC_PATH);
+ assertEquals("/hop", StatusServletUtils.HOP_ROOT);
}
}
diff --git a/engine/src/test/java/org/apache/hop/www/WebResultTest.java b/engine/src/test/java/org/apache/hop/www/WebResultTest.java
index 641afabb60..206efe2a48 100644
--- a/engine/src/test/java/org/apache/hop/www/WebResultTest.java
+++ b/engine/src/test/java/org/apache/hop/www/WebResultTest.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.UUID;
import org.apache.hop.core.exception.HopXmlException;
@@ -75,6 +76,16 @@ class WebResultTest {
assertEquals(original.getId(), copy.getId());
}
+ @Test
+ void testGetJsonSerializesBeanFields() {
+ WebResult result = new WebResult(WebResult.STRING_ERROR, "msg", "id1");
+ String json = result.getJson();
+ assertTrue(json.contains("\"result\""));
+ assertTrue(json.contains(WebResult.STRING_ERROR));
+ assertTrue(json.contains("msg"));
+ assertTrue(json.contains("id1"));
+ }
+
@Test
void testSetters() {
WebResult result = new WebResult("");
diff --git a/engine/src/test/java/org/apache/hop/www/WebServerShutdownHookTest.java b/engine/src/test/java/org/apache/hop/www/WebServerShutdownHookTest.java
new file mode 100644
index 0000000000..2768128e2f
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/WebServerShutdownHookTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.jupiter.api.Test;
+
+class WebServerShutdownHookTest {
+
+ @Test
+ void runInvokesStopServerWhenNotShuttingDown() throws Exception {
+ WebServer webServer = mock(WebServer.class);
+ WebServerShutdownHook hook = new WebServerShutdownHook(webServer);
+ hook.run();
+ verify(webServer).stopServer();
+ }
+
+ @Test
+ void runSwallowsStopServerException() throws Exception {
+ WebServer webServer = mock(WebServer.class);
+ doThrow(new RuntimeException("stop failed")).when(webServer).stopServer();
+ WebServerShutdownHook hook = new WebServerShutdownHook(webServer);
+ hook.run();
+ verify(webServer).stopServer();
+ }
+
+ @Test
+ void runSkipsStopServerWhenAlreadyShuttingDown() throws Exception {
+ WebServer webServer = mock(WebServer.class);
+ WebServerShutdownHook hook = new WebServerShutdownHook(webServer);
+ hook.setShuttingDown(true);
+ hook.run();
+ verify(webServer, never()).stopServer();
+ }
+
+ @Test
+ void shuttingDownFlag() {
+ WebServerShutdownHook hook = new WebServerShutdownHook(mock(WebServer.class));
+ hook.setShuttingDown(true);
+ assertTrue(hook.isShuttingDown());
+ hook.setShuttingDown(false);
+ assertFalse(hook.isShuttingDown());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/WorkflowMapTest.java b/engine/src/test/java/org/apache/hop/www/WorkflowMapTest.java
new file mode 100644
index 0000000000..d4259fda87
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/WorkflowMapTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.www;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import org.apache.hop.workflow.WorkflowConfiguration;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.engine.IWorkflowEngine;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class WorkflowMapTest {
+
+ private WorkflowMap map;
+
+ @BeforeEach
+ void setUp() {
+ map = new WorkflowMap();
+ }
+
+ @Test
+ void addWorkflowValidatesNameAndId() {
+ IWorkflowEngine<WorkflowMeta> wf = mock(IWorkflowEngine.class);
+ WorkflowConfiguration cfg = mock(WorkflowConfiguration.class);
+ assertThrows(RuntimeException.class, () -> map.addWorkflow("", "id", wf, cfg));
+ assertThrows(RuntimeException.class, () -> map.addWorkflow("n", null, wf, cfg));
+ }
+
+ @Test
+ void addGetRemoveFindAndList() {
+ IWorkflowEngine<WorkflowMeta> wf = mock(IWorkflowEngine.class);
+ when(wf.getWorkflowName()).thenReturn("W1");
+ when(wf.getContainerId()).thenReturn("cid-w");
+ WorkflowConfiguration cfg = mock(WorkflowConfiguration.class);
+
+ map.addWorkflow("W1", "cid-w", wf, cfg);
+
+ HopServerObjectEntry entry = new HopServerObjectEntry("W1", "cid-w");
+ assertSame(wf, map.getWorkflow(entry));
+ assertSame(wf, map.getWorkflow("W1"));
+ assertSame(cfg, map.getConfiguration(entry));
+ assertSame(wf, map.findWorkflow("cid-w"));
+ assertSame(wf, map.findWorkflow("W1", "cid-w"));
+ assertSame(wf, map.findWorkflow("W1", null));
+
+ assertNotNull(map.getFirstHopServerObjectEntry("W1"));
+
+ List<HopServerObjectEntry> keys = map.getWorkflowObjects();
+ assertEquals(1, keys.size());
+
+ map.removeWorkflow(entry);
+ assertNull(map.getWorkflow(entry));
+ assertTrue(map.getWorkflowObjects().isEmpty());
+ }
+
+ @Test
+ void replaceWorkflowReplacesExisting() {
+ IWorkflowEngine<WorkflowMeta> oldWf = mock(IWorkflowEngine.class);
+ when(oldWf.getWorkflowName()).thenReturn("W2");
+ when(oldWf.getContainerId()).thenReturn("id2");
+ WorkflowConfiguration cfg = mock(WorkflowConfiguration.class);
+ map.addWorkflow("W2", "id2", oldWf, cfg);
+
+ IWorkflowEngine<WorkflowMeta> newWf = mock(IWorkflowEngine.class);
+ when(newWf.getWorkflowName()).thenReturn("W2");
+ when(newWf.getContainerId()).thenReturn("id2");
+
+ map.replaceWorkflow(oldWf, newWf, cfg);
+ assertSame(newWf, map.getWorkflow("W2"));
+ }
+
+ @Test
+ void hopServerConfigRoundTrip() {
+ HopServerConfig cfg = mock(HopServerConfig.class);
+ map.setHopServerConfig(cfg);
+ assertSame(cfg, map.getHopServerConfig());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/jaxrs/NVPairTest.java b/engine/src/test/java/org/apache/hop/www/jaxrs/NVPairTest.java
new file mode 100644
index 0000000000..64e575263c
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/jaxrs/NVPairTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.www.jaxrs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+class NVPairTest {
+
+ @Test
+ void defaultConstructor() {
+ NVPair p = new NVPair();
+ assertNull(p.getName());
+ assertNull(p.getValue());
+ }
+
+ @Test
+ void valueConstructorAndSetters() {
+ NVPair p = new NVPair("k", "v");
+ assertEquals("k", p.getName());
+ assertEquals("v", p.getValue());
+ p.setName("k2");
+ p.setValue("v2");
+ assertEquals("k2", p.getName());
+ assertEquals("v2", p.getValue());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/jaxrs/PipelineStatusTest.java b/engine/src/test/java/org/apache/hop/www/jaxrs/PipelineStatusTest.java
new file mode 100644
index 0000000000..96bfb6dac3
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/jaxrs/PipelineStatusTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.www.jaxrs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import org.apache.hop.pipeline.transform.TransformStatus;
+import org.junit.jupiter.api.Test;
+
+class PipelineStatusTest {
+
+ @Test
+ void accessorsAndAddTransformStatus() {
+ PipelineStatus ps = new PipelineStatus();
+ ps.setId("id1");
+ ps.setName("pipe");
+ ps.setStatus("Running");
+ assertEquals("id1", ps.getId());
+ assertEquals("pipe", ps.getName());
+ assertEquals("Running", ps.getStatus());
+
+ TransformStatus ts = new TransformStatus();
+ ps.addTransformStatus(ts);
+ assertEquals(1, ps.getTransformStatuses().size());
+ assertSame(ts, ps.getTransformStatuses().get(0));
+
+ ps.setTransformStatuses(Collections.emptyList());
+ assertTrue(ps.getTransformStatuses().isEmpty());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/jaxrs/ServerStatusTest.java b/engine/src/test/java/org/apache/hop/www/jaxrs/ServerStatusTest.java
new file mode 100644
index 0000000000..c45d1bdd1d
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/jaxrs/ServerStatusTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.www.jaxrs;
+
+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 org.junit.jupiter.api.Test;
+
+class ServerStatusTest {
+
+ @Test
+ void defaultConstructorPopulatesRuntimeMetrics() {
+ ServerStatus status = new ServerStatus();
+ assertTrue(status.getMemoryTotal() > 0);
+ assertNotNull(status.getOsName());
+ assertTrue(status.getCpuCores() > 0);
+ }
+
+ @Test
+ void statusDescriptionConstructor() {
+ ServerStatus status = new ServerStatus("Online");
+ assertEquals("Online", status.getStatusDescription());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/jaxrs/WorkflowStatusTest.java b/engine/src/test/java/org/apache/hop/www/jaxrs/WorkflowStatusTest.java
new file mode 100644
index 0000000000..e0b4690c38
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/jaxrs/WorkflowStatusTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.www.jaxrs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class WorkflowStatusTest {
+
+ @Test
+ void accessors() {
+ WorkflowStatus ws = new WorkflowStatus();
+ ws.setId("w1");
+ ws.setName("wf");
+ ws.setStatus("Finished");
+ assertEquals("w1", ws.getId());
+ assertEquals("wf", ws.getName());
+ assertEquals("Finished", ws.getStatus());
+ }
+}
diff --git a/engine/src/test/java/org/apache/hop/www/service/WebServiceTest.java b/engine/src/test/java/org/apache/hop/www/service/WebServiceTest.java
new file mode 100644
index 0000000000..f84b8c043a
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/www/service/WebServiceTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.www.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class WebServiceTest {
+
+ @Test
+ void defaultConstructorAndSetters() {
+ WebService ws = new WebService();
+ ws.setName("svc1");
+ ws.setEnabled(true);
+ ws.setFilename("/path/pipeline.hpl");
+ ws.setTransformName("t1");
+ ws.setFieldName("f1");
+ ws.setContentType("application/json");
+ ws.setStatusCode("200");
+ ws.setListingStatus(true);
+ ws.setBodyContentVariable("bodyVar");
+ ws.setRunConfigurationName("local");
+ ws.setHeaderContentVariable("hdrVar");
+
+ assertEquals("svc1", ws.getName());
+ assertTrue(ws.isEnabled());
+ assertEquals("/path/pipeline.hpl", ws.getFilename());
+ assertEquals("t1", ws.getTransformName());
+ assertEquals("f1", ws.getFieldName());
+ assertEquals("application/json", ws.getContentType());
+ assertEquals("200", ws.getStatusCode());
+ assertTrue(ws.isListingStatus());
+ assertEquals("bodyVar", ws.getBodyContentVariable());
+ assertEquals("local", ws.getRunConfigurationName());
+ assertEquals("hdrVar", ws.getHeaderContentVariable());
+ }
+
+ @Test
+ void fullConstructor() {
+ WebService ws =
+ new WebService(
+ "n", false, "file.hpl", "tr", "fld", "text/plain", "201", false, "b", "run", "h");
+ assertEquals("n", ws.getName());
+ assertFalse(ws.isEnabled());
+ assertEquals("file.hpl", ws.getFilename());
+ assertEquals("h", ws.getHeaderContentVariable());
+ }
+}
diff --git a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatusServlet.java b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatusServlet.java
index bc36f2cf62..25451e4726 100644
--- a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatusServlet.java
+++ b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatusServlet.java
@@ -91,14 +91,20 @@ public class AsyncStatusServlet extends BaseHttpServlet implements IHopServerPlu
String webServiceName = request.getParameter("service");
if (StringUtils.isEmpty(webServiceName)) {
- throw new ServletException(
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_BAD_REQUEST,
"Please specify a service parameter pointing to the name of the asynchronous webservice object");
+ return;
}
String serverObjectId = request.getParameter("id");
if (StringUtils.isEmpty(serverObjectId)) {
- throw new ServletException(
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_BAD_REQUEST,
"Please specify an id parameter pointing to the unique ID of the asynchronous webservice object");
+ return;
}
try {
@@ -161,7 +167,11 @@ public class AsyncStatusServlet extends BaseHttpServlet implements IHopServerPlu
response.setStatus(HttpServletResponse.SC_OK);
} catch (Exception e) {
- throw new ServletException("Error getting asynchronous web service status", e);
+ logError("Error getting asynchronous web service status", e);
+ sendSafeError(
+ response,
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Error getting asynchronous web service status.");
}
}