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 8a6fca06e4 Add header variable support to Asynchronous web service, 
fixes #5850 (#5875)
8a6fca06e4 is described below

commit 8a6fca06e4aa2c5616fc7bb6ed6beda8bd848f5a
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Oct 23 14:39:02 2025 +0200

    Add header variable support to Asynchronous web service, fixes #5850 (#5875)
---
 .../org/apache/hop/www/async/AsyncRunServlet.java  |  21 ++++
 .../java/org/apache/hop/www/async/AsyncStatus.java | 132 +-------------------
 .../org/apache/hop/www/async/AsyncWebService.java  |  91 ++------------
 .../hop/www/async/AsyncWebServiceEditor.java       |  45 +++++--
 .../www/async/messages/messages_en_US.properties   |   8 +-
 .../apache/hop/www/async/AsyncGuiPluginTest.java   |  92 ++++++++++++++
 .../org/apache/hop/www/async/AsyncStatusTest.java  | 111 +++++++++++++++++
 .../apache/hop/www/async/AsyncWebServiceTest.java  | 137 +++++++++++++++++++++
 .../org/apache/hop/www/async/DefaultsTest.java     |  64 ++++++++++
 .../hop/ui/www/service/WebServiceEditor.java       |   6 +-
 .../www/service/messages/messages_en_US.properties |   2 +
 11 files changed, 484 insertions(+), 225 deletions(-)

diff --git 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncRunServlet.java
 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncRunServlet.java
index 99e16a9e78..841f5f546c 100644
--- 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncRunServlet.java
+++ 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncRunServlet.java
@@ -17,6 +17,8 @@
 
 package org.apache.hop.www.async;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -26,6 +28,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.UUID;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
@@ -190,6 +193,24 @@ public class AsyncRunServlet extends BaseHttpServlet 
implements IHopServerPlugin
           }
         }
         workflow.setVariable(contentVariable, Const.NVL(content, ""));
+
+        String headerContentVariable = 
variables.resolve(webService.getHeaderContentVariable());
+        String headerContent = "";
+        if (StringUtils.isNotEmpty(headerContentVariable)) {
+          // Create JSON object containing all request headers
+          ObjectMapper objectMapper = new ObjectMapper();
+          ObjectNode headersJson = objectMapper.createObjectNode();
+
+          Enumeration<String> headerNames = request.getHeaderNames();
+          while (headerNames.hasMoreElements()) {
+            String headerName = headerNames.nextElement();
+            String headerValue = request.getHeader(headerName);
+            headersJson.put(headerName, headerValue);
+          }
+          headerContent = objectMapper.writeValueAsString(headersJson);
+        }
+
+        workflow.setVariable(headerContentVariable, headerContent);
       }
 
       // Set all the other parameters as variables/parameters...
diff --git 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatus.java 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatus.java
index 923f28601b..18d71b24ac 100644
--- a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatus.java
+++ b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncStatus.java
@@ -23,8 +23,12 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.www.HopServerPipelineStatus;
 
+@Getter
+@Setter
 public class AsyncStatus {
   private String service;
   private String id;
@@ -47,132 +51,4 @@ public class AsyncStatus {
     statusVariables = new HashMap<>();
     pipelineStatuses = new ArrayList<>();
   }
-
-  /**
-   * Gets service
-   *
-   * @return value of service
-   */
-  public String getService() {
-    return service;
-  }
-
-  /**
-   * @param service The service to set
-   */
-  public void setService(String service) {
-    this.service = service;
-  }
-
-  /**
-   * Gets id
-   *
-   * @return value of id
-   */
-  public String getId() {
-    return id;
-  }
-
-  /**
-   * @param id The id to set
-   */
-  public void setId(String id) {
-    this.id = id;
-  }
-
-  /**
-   * Gets logDate
-   *
-   * @return value of logDate
-   */
-  public Date getLogDate() {
-    return logDate;
-  }
-
-  /**
-   * @param logDate The logDate to set
-   */
-  public void setLogDate(Date logDate) {
-    this.logDate = logDate;
-  }
-
-  /**
-   * Gets startDate
-   *
-   * @return value of startDate
-   */
-  public Date getStartDate() {
-    return startDate;
-  }
-
-  /**
-   * @param startDate The startDate to set
-   */
-  public void setStartDate(Date startDate) {
-    this.startDate = startDate;
-  }
-
-  /**
-   * Gets endDate
-   *
-   * @return value of endDate
-   */
-  public Date getEndDate() {
-    return endDate;
-  }
-
-  /**
-   * @param endDate The endDate to set
-   */
-  public void setEndDate(Date endDate) {
-    this.endDate = endDate;
-  }
-
-  /**
-   * Gets statusDescription
-   *
-   * @return value of statusDescription
-   */
-  public String getStatusDescription() {
-    return statusDescription;
-  }
-
-  /**
-   * @param statusDescription The statusDescription to set
-   */
-  public void setStatusDescription(String statusDescription) {
-    this.statusDescription = statusDescription;
-  }
-
-  /**
-   * Gets statusVariables
-   *
-   * @return value of statusVariables
-   */
-  public Map<String, String> getStatusVariables() {
-    return statusVariables;
-  }
-
-  /**
-   * @param statusVariables The statusVariables to set
-   */
-  public void setStatusVariables(Map<String, String> statusVariables) {
-    this.statusVariables = statusVariables;
-  }
-
-  /**
-   * Gets pipelineStatuses
-   *
-   * @return value of pipelineStatuses
-   */
-  public List<HopServerPipelineStatus> getPipelineStatuses() {
-    return pipelineStatuses;
-  }
-
-  /**
-   * @param pipelineStatuses The pipelineStatuses to set
-   */
-  public void setPipelineStatuses(List<HopServerPipelineStatus> 
pipelineStatuses) {
-    this.pipelineStatuses = pipelineStatuses;
-  }
 }
diff --git 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebService.java
 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebService.java
index fb7ec49971..f78a38860e 100644
--- 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebService.java
+++ 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebService.java
@@ -19,6 +19,8 @@ package org.apache.hop.www.async;
 
 import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.variables.IVariables;
@@ -35,6 +37,8 @@ import org.apache.hop.metadata.api.IHopMetadata;
     image = "ui/images/server.svg",
     documentationUrl = "/metadata-types/async-web-service.html",
     hopMetadataPropertyType = HopMetadataPropertyType.SERVER_WEB_SERVICE_ASYNC)
+@Getter
+@Setter
 public class AsyncWebService extends HopMetadataBase implements IHopMetadata {
 
   @HopMetadataProperty private boolean enabled;
@@ -42,6 +46,7 @@ public class AsyncWebService extends HopMetadataBase 
implements IHopMetadata {
   @HopMetadataProperty private String statusVariables;
   @HopMetadataProperty private String bodyContentVariable;
   @HopMetadataProperty private String runConfigurationName;
+  @HopMetadataProperty private String headerContentVariable;
 
   public AsyncWebService() {
     this.enabled = true;
@@ -54,13 +59,15 @@ public class AsyncWebService extends HopMetadataBase 
implements IHopMetadata {
       String filename,
       String statusVariables,
       String bodyContentVariable,
-      String runConfigurationName) {
+      String runConfigurationName,
+      String headerContentVariable) {
     super(name);
     this.enabled = enabled;
     this.filename = filename;
     this.statusVariables = statusVariables;
     this.bodyContentVariable = bodyContentVariable;
     this.runConfigurationName = runConfigurationName;
+    this.headerContentVariable = headerContentVariable;
   }
 
   /**
@@ -80,86 +87,4 @@ public class AsyncWebService extends HopMetadataBase 
implements IHopMetadata {
     }
     return list;
   }
-
-  /**
-   * Gets enabled
-   *
-   * @return value of enabled
-   */
-  public boolean isEnabled() {
-    return enabled;
-  }
-
-  /**
-   * @param enabled The enabled to set
-   */
-  public void setEnabled(boolean enabled) {
-    this.enabled = enabled;
-  }
-
-  /**
-   * Gets filename
-   *
-   * @return value of filename
-   */
-  public String getFilename() {
-    return filename;
-  }
-
-  /**
-   * @param filename The filename to set
-   */
-  public void setFilename(String filename) {
-    this.filename = filename;
-  }
-
-  /**
-   * Gets statusVariables
-   *
-   * @return value of statusVariables
-   */
-  public String getStatusVariables() {
-    return statusVariables;
-  }
-
-  /**
-   * @param statusVariables The statusVariables to set
-   */
-  public void setStatusVariables(String statusVariables) {
-    this.statusVariables = statusVariables;
-  }
-
-  /**
-   * Gets bodyContentVariable
-   *
-   * @return value of bodyContentVariable
-   */
-  public String getBodyContentVariable() {
-    return bodyContentVariable;
-  }
-
-  /**
-   * @param bodyContentVariable The bodyContentVariable to set
-   */
-  public void setBodyContentVariable(String bodyContentVariable) {
-    this.bodyContentVariable = bodyContentVariable;
-  }
-
-  /**
-   * Gets runConfigurationName
-   *
-   * @return value of runConfigurationName
-   */
-  public String getRunConfigurationName() {
-    return runConfigurationName;
-  }
-
-  /**
-   * Sets runConfigurationName
-   *
-   * @param runConfigurationName value of runConfigurationName
-   */
-  public void setRunConfigurationName(String runConfigurationName) {
-    this.runConfigurationName = runConfigurationName;
-  }
 }
diff --git 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebServiceEditor.java
 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebServiceEditor.java
index 315cb45ce3..18c514a185 100644
--- 
a/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebServiceEditor.java
+++ 
b/plugins/misc/async/src/main/java/org/apache/hop/www/async/AsyncWebServiceEditor.java
@@ -32,7 +32,6 @@ import org.apache.hop.ui.core.widget.TextVar;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.file.workflow.HopWorkflowFileType;
 import 
org.apache.hop.ui.hopgui.perspective.dataorch.HopDataOrchestrationPerspective;
-import org.apache.hop.ui.www.service.WebServiceEditor;
 import org.apache.hop.workflow.WorkflowMeta;
 import org.apache.hop.workflow.config.WorkflowRunConfiguration;
 import org.eclipse.swt.SWT;
@@ -51,7 +50,7 @@ import org.eclipse.swt.widgets.Text;
  * @see AsyncWebService
  */
 public class AsyncWebServiceEditor extends MetadataEditor<AsyncWebService> {
-  private static final Class<?> PKG = WebServiceEditor.class;
+  private static final Class<?> PKG = AsyncWebServiceEditor.class;
 
   private Text wName;
   private Button wEnabled;
@@ -59,6 +58,7 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
   private MetaSelectionLine<WorkflowRunConfiguration> wRunConfiguration;
   private TextVar wStatusVars;
   private TextVar wContentVar;
+  private TextVar wHeaderContentVariable;
 
   public AsyncWebServiceEditor(
       HopGui hopGui, MetadataManager<AsyncWebService> manager, AsyncWebService 
metadata) {
@@ -177,9 +177,8 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
             WorkflowRunConfiguration.class,
             parent,
             SWT.NONE,
-            "Workflow run configuration",
-            "This is the workflow run configuration to use on the server. "
-                + "If left blank a standard local workflow engine is used.");
+            BaseMessages.getString(PKG, 
"AsyncWebService.Runconfiguration.Label"),
+            BaseMessages.getString(PKG, 
"AsyncWebService.Runconfiguration.Tooltip"));
     FormData fdRunConfiguration = new FormData();
     fdRunConfiguration.left = new FormAttachment(0, 0);
     fdRunConfiguration.top = new FormAttachment(lastControl, margin);
@@ -191,7 +190,7 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     //
     Label wlStatusVars = new Label(parent, SWT.RIGHT);
     PropsUi.setLook(wlStatusVars);
-    wlStatusVars.setText("Status variables (, separated)");
+    wlStatusVars.setText(BaseMessages.getString(PKG, 
"AsyncWebService.StatusVariables.Label"));
     FormData fdlStatusVars = new FormData();
     fdlStatusVars.left = new FormAttachment(0, 0);
     fdlStatusVars.right = new FormAttachment(middle, -margin);
@@ -206,11 +205,10 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     wStatusVars.setLayoutData(fdStatusVars);
     lastControl = wlStatusVars;
 
-    // Status variables
-    //
+    // body content variables
     Label wlContentVar = new Label(parent, SWT.RIGHT);
     PropsUi.setLook(wlContentVar);
-    wlContentVar.setText("Content variable");
+    wlContentVar.setText(BaseMessages.getString(PKG, 
"AsyncWebService.BodyContentVariable.Label"));
     FormData fdlContentVar = new FormData();
     fdlContentVar.left = new FormAttachment(0, 0);
     fdlContentVar.right = new FormAttachment(middle, -margin);
@@ -223,6 +221,32 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     fdContentVar.right = new FormAttachment(100, 0);
     fdContentVar.top = new FormAttachment(wlContentVar, 0, SWT.CENTER);
     wContentVar.setLayoutData(fdContentVar);
+    lastControl = wContentVar;
+
+    // HeaderContentVariable to read from
+    //
+    Label wlHeaderContentVariable = new Label(parent, SWT.RIGHT);
+    PropsUi.setLook(wlHeaderContentVariable);
+    wlHeaderContentVariable.setText(
+        BaseMessages.getString(PKG, 
"AsyncWebService.HeaderContentVariable.Label"));
+    wlHeaderContentVariable.setToolTipText(
+        BaseMessages.getString(PKG, 
"AsyncWebService.HeaderContentVariable.Tooltip"));
+    FormData fdlHeaderContentVariable = new FormData();
+    fdlHeaderContentVariable.left = new FormAttachment(0, 0);
+    fdlHeaderContentVariable.right = new FormAttachment(middle, -margin);
+    fdlHeaderContentVariable.top = new FormAttachment(lastControl, 2 * margin);
+    wlHeaderContentVariable.setLayoutData(fdlHeaderContentVariable);
+    wHeaderContentVariable =
+        new TextVar(manager.getVariables(), parent, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    wHeaderContentVariable.setToolTipText(
+        BaseMessages.getString(PKG, 
"AsyncWebService.HeaderContentVariable.Tooltip"));
+    PropsUi.setLook(wHeaderContentVariable);
+    FormData fdHeaderContentVariable = new FormData();
+    fdHeaderContentVariable.left = new FormAttachment(middle, 0);
+    fdHeaderContentVariable.right = new FormAttachment(100, 0);
+    fdHeaderContentVariable.top = new FormAttachment(wlHeaderContentVariable, 
0, SWT.CENTER);
+    wHeaderContentVariable.setLayoutData(fdHeaderContentVariable);
+    lastControl = wlHeaderContentVariable;
 
     setWidgetsContent();
 
@@ -234,6 +258,7 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     wRunConfiguration.addListener(SWT.Modify, lsMod);
     wStatusVars.addListener(SWT.Modify, lsMod);
     wContentVar.addListener(SWT.Modify, lsMod);
+    wHeaderContentVariable.addListener(SWT.Modify, lsMod);
   }
 
   /**
@@ -333,6 +358,7 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     wFilename.setText(Const.NVL(ws.getFilename(), ""));
     wStatusVars.setText(Const.NVL(ws.getStatusVariables(), ""));
     wContentVar.setText(Const.NVL(ws.getBodyContentVariable(), ""));
+    wHeaderContentVariable.setText(Const.NVL(ws.getHeaderContentVariable(), 
""));
     try {
       wRunConfiguration.fillItems();
       wRunConfiguration.setText(Const.NVL(ws.getRunConfigurationName(), ""));
@@ -349,6 +375,7 @@ public class AsyncWebServiceEditor extends 
MetadataEditor<AsyncWebService> {
     ws.setStatusVariables(wStatusVars.getText());
     ws.setBodyContentVariable(wContentVar.getText());
     ws.setRunConfigurationName(wRunConfiguration.getText());
+    ws.setHeaderContentVariable(wHeaderContentVariable.getText());
   }
 
   @Override
diff --git 
a/plugins/misc/async/src/main/resources/org/apache/hop/www/async/messages/messages_en_US.properties
 
b/plugins/misc/async/src/main/resources/org/apache/hop/www/async/messages/messages_en_US.properties
index 9687e2209c..0b8ec7ca9d 100644
--- 
a/plugins/misc/async/src/main/resources/org/apache/hop/www/async/messages/messages_en_US.properties
+++ 
b/plugins/misc/async/src/main/resources/org/apache/hop/www/async/messages/messages_en_US.properties
@@ -26,4 +26,10 @@ AsyncWebServiceEditor.Filename.Label=The workflow filename
 AsyncWebServiceEditor.Name.Label=Asynchronous Web Service
 AsyncWebServiceEditor.StatusVars.Label=The status variables (, separated)
 AsyncWebService.name=Asynchronous Web Service
-AsyncWebService.description=Allows you to run a long running workflow 
asynchronously
\ No newline at end of file
+AsyncWebService.description=Allows you to run a long running workflow 
asynchronously
+AsyncWebService.BodyContentVariable.Label=Request body content variable
+AsyncWebService.HeaderContentVariable.Label=Request header content variable
+AsyncWebService.HeaderContentVariable.Tooltip=This is the name of the variable 
which at runtime will contain the content of the request header content.
+AsyncWebService.StatusVariables.Label=Status variables (, separated)
+AsyncWebService.Runconfiguration.Label=Workflow run configuration
+AsyncWebService.Runconfiguration.Tooltip=This is the workflow run 
configuration to use on the server. If left blank a standard local workflow 
engine is used.
\ No newline at end of file
diff --git 
a/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncGuiPluginTest.java
 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncGuiPluginTest.java
new file mode 100644
index 0000000000..e515cff07c
--- /dev/null
+++ 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncGuiPluginTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.async;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/** Test class for AsyncGuiPlugin */
+public class AsyncGuiPluginTest {
+
+  @Test
+  public void testGetInstance() {
+    AsyncGuiPlugin instance1 = AsyncGuiPlugin.getInstance();
+    AsyncGuiPlugin instance2 = AsyncGuiPlugin.getInstance();
+
+    assertNotNull(instance1);
+    assertSame(instance1, instance2);
+  }
+
+  @Test
+  public void testConstructor() {
+    AsyncGuiPlugin plugin = new AsyncGuiPlugin();
+    assertNotNull(plugin);
+  }
+
+  @Test
+  public void testActionIds() {
+    
assertNotNull(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_ENABLE_ASYNC_LOGGING);
+    
assertNotNull(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_DISABLE_ASYNC_LOGGING);
+
+    
assertTrue(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_ENABLE_ASYNC_LOGGING.contains("enable"));
+    
assertTrue(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_DISABLE_ASYNC_LOGGING.contains("disable"));
+
+    
assertTrue(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_ENABLE_ASYNC_LOGGING.contains("async"));
+    
assertTrue(AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_DISABLE_ASYNC_LOGGING.contains("async"));
+  }
+
+  @Test
+  public void testActionIdUniqueness() {
+    assertTrue(
+        !AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_ENABLE_ASYNC_LOGGING.equals(
+            AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_DISABLE_ASYNC_LOGGING));
+  }
+
+  @Test
+  public void testActionIdFormat() {
+    String enableId = 
AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_ENABLE_ASYNC_LOGGING;
+    String disableId = 
AsyncGuiPlugin.ACTION_ID_WORKFLOW_GRAPH_DISABLE_ASYNC_LOGGING;
+
+    assertTrue(enableId.startsWith("workflow-graph-action"));
+    assertTrue(disableId.startsWith("workflow-graph-action"));
+
+    assertTrue(enableId.matches(".*\\d+.*"));
+    assertTrue(disableId.matches(".*\\d+.*"));
+  }
+
+  @Test
+  public void testSingletonBehavior() {
+    AsyncGuiPlugin instance1 = AsyncGuiPlugin.getInstance();
+    AsyncGuiPlugin instance2 = AsyncGuiPlugin.getInstance();
+    AsyncGuiPlugin instance3 = AsyncGuiPlugin.getInstance();
+
+    assertSame(instance1, instance2);
+    assertSame(instance2, instance3);
+    assertSame(instance1, instance3);
+  }
+
+  @Test
+  public void testInstanceNotNull() {
+    AsyncGuiPlugin instance = AsyncGuiPlugin.getInstance();
+    assertNotNull(instance);
+    assertTrue(instance instanceof AsyncGuiPlugin);
+  }
+}
diff --git 
a/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncStatusTest.java
 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncStatusTest.java
new file mode 100644
index 0000000000..819850a621
--- /dev/null
+++ 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncStatusTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.async;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.hop.www.HopServerPipelineStatus;
+import org.junit.jupiter.api.Test;
+
+/** Test class for AsyncStatus */
+public class AsyncStatusTest {
+
+  @Test
+  public void testDefaultConstructor() {
+    AsyncStatus status = new AsyncStatus();
+
+    assertNotNull(status.getLogDate());
+    assertNotNull(status.getStatusVariables());
+    assertNotNull(status.getPipelineStatuses());
+
+    assertTrue(status.getStatusVariables().isEmpty());
+    assertTrue(status.getPipelineStatuses().isEmpty());
+  }
+
+  @Test
+  public void testSettersAndGetters() {
+    AsyncStatus status = new AsyncStatus();
+
+    String service = "test-service";
+    status.setService(service);
+    assertEquals(service, status.getService());
+
+    String id = "test-id-123";
+    status.setId(id);
+    assertEquals(id, status.getId());
+
+    Date logDate = new Date();
+    Date startDate = new Date(System.currentTimeMillis() - 1000);
+    Date endDate = new Date();
+
+    status.setLogDate(logDate);
+    status.setStartDate(startDate);
+    status.setEndDate(endDate);
+
+    assertEquals(logDate, status.getLogDate());
+    assertEquals(startDate, status.getStartDate());
+    assertEquals(endDate, status.getEndDate());
+
+    String statusDescription = "Running";
+    status.setStatusDescription(statusDescription);
+    assertEquals(statusDescription, status.getStatusDescription());
+  }
+
+  @Test
+  public void testStatusVariables() {
+    AsyncStatus status = new AsyncStatus();
+    Map<String, String> variables = new HashMap<>();
+
+    variables.put("VAR1", "value1");
+    variables.put("VAR2", "value2");
+    variables.put("VAR3", "value3");
+
+    status.setStatusVariables(variables);
+
+    assertEquals(variables, status.getStatusVariables());
+    assertEquals(3, status.getStatusVariables().size());
+    assertEquals("value1", status.getStatusVariables().get("VAR1"));
+    assertEquals("value2", status.getStatusVariables().get("VAR2"));
+    assertEquals("value3", status.getStatusVariables().get("VAR3"));
+  }
+
+  @Test
+  public void testPipelineStatuses() {
+    AsyncStatus status = new AsyncStatus();
+
+    HopServerPipelineStatus pipeline1 = new HopServerPipelineStatus();
+    pipeline1.setPipelineName("pipeline1");
+    pipeline1.setStatusDescription("Running");
+
+    HopServerPipelineStatus pipeline2 = new HopServerPipelineStatus();
+    pipeline2.setPipelineName("pipeline2");
+    pipeline2.setStatusDescription("Finished");
+
+    status.getPipelineStatuses().add(pipeline1);
+    status.getPipelineStatuses().add(pipeline2);
+
+    assertEquals(2, status.getPipelineStatuses().size());
+    assertTrue(status.getPipelineStatuses().contains(pipeline1));
+    assertTrue(status.getPipelineStatuses().contains(pipeline2));
+  }
+}
diff --git 
a/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncWebServiceTest.java
 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncWebServiceTest.java
new file mode 100644
index 0000000000..28d9bf9668
--- /dev/null
+++ 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/AsyncWebServiceTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.async;
+
+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 java.util.List;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.Test;
+
+/** Test class for AsyncWebService */
+public class AsyncWebServiceTest {
+
+  @Test
+  public void testDefaultConstructor() {
+    AsyncWebService webService = new AsyncWebService();
+
+    assertTrue(webService.isEnabled());
+    assertEquals("ASYNC_CONTENT", webService.getBodyContentVariable());
+  }
+
+  @Test
+  public void testParameterizedConstructor() {
+    String name = "test-service";
+    boolean enabled = false;
+    String filename = "/path/to/workflow.hwf";
+    String statusVariables = "VAR1,VAR2,VAR3";
+    String bodyContentVariable = "CUSTOM_CONTENT";
+    String runConfigurationName = "test-run-config";
+    String headerContentVariable = "HEADER_CONTENT";
+
+    AsyncWebService webService =
+        new AsyncWebService(
+            name,
+            enabled,
+            filename,
+            statusVariables,
+            bodyContentVariable,
+            runConfigurationName,
+            headerContentVariable);
+
+    assertEquals(name, webService.getName());
+    assertEquals(enabled, webService.isEnabled());
+    assertEquals(filename, webService.getFilename());
+    assertEquals(statusVariables, webService.getStatusVariables());
+    assertEquals(bodyContentVariable, webService.getBodyContentVariable());
+    assertEquals(runConfigurationName, webService.getRunConfigurationName());
+    assertEquals(headerContentVariable, webService.getHeaderContentVariable());
+  }
+
+  @Test
+  public void testSettersAndGetters() {
+    AsyncWebService webService = new AsyncWebService();
+
+    webService.setEnabled(false);
+    assertFalse(webService.isEnabled());
+    webService.setEnabled(true);
+    assertTrue(webService.isEnabled());
+
+    String filename = "/test/workflow.hwf";
+    webService.setFilename(filename);
+    assertEquals(filename, webService.getFilename());
+
+    String statusVariables = "STATUS_VAR1,STATUS_VAR2";
+    webService.setStatusVariables(statusVariables);
+    assertEquals(statusVariables, webService.getStatusVariables());
+
+    String bodyContentVariable = "BODY_VAR";
+    webService.setBodyContentVariable(bodyContentVariable);
+    assertEquals(bodyContentVariable, webService.getBodyContentVariable());
+
+    String runConfigurationName = "RUN_CONFIG";
+    webService.setRunConfigurationName(runConfigurationName);
+    assertEquals(runConfigurationName, webService.getRunConfigurationName());
+
+    String headerContentVariable = "HEADER_VAR";
+    webService.setHeaderContentVariable(headerContentVariable);
+    assertEquals(headerContentVariable, webService.getHeaderContentVariable());
+  }
+
+  @Test
+  public void testGetStatusVariablesList() {
+    AsyncWebService webService = new AsyncWebService();
+    Variables variables = new Variables();
+
+    webService.setStatusVariables("");
+    List<String> emptyList = webService.getStatusVariablesList(variables);
+    assertTrue(emptyList.isEmpty());
+
+    webService.setStatusVariables(null);
+    List<String> nullList = webService.getStatusVariablesList(variables);
+    assertTrue(nullList.isEmpty());
+
+    webService.setStatusVariables("VAR1");
+    List<String> singleList = webService.getStatusVariablesList(variables);
+    assertEquals(1, singleList.size());
+    assertEquals("VAR1", singleList.get(0));
+
+    webService.setStatusVariables("VAR1,VAR2,VAR3");
+    List<String> multipleList = webService.getStatusVariablesList(variables);
+    assertEquals(3, multipleList.size());
+    assertEquals("VAR1", multipleList.get(0));
+    assertEquals("VAR2", multipleList.get(1));
+    assertEquals("VAR3", multipleList.get(2));
+  }
+
+  @Test
+  public void testGetStatusVariablesListWithVariableResolution() {
+    AsyncWebService webService = new AsyncWebService();
+    Variables variables = new Variables();
+
+    variables.setVariable("STATUS_VARS", "RESOLVED_VAR1,RESOLVED_VAR2");
+    webService.setStatusVariables("${STATUS_VARS}");
+
+    List<String> resolvedList = webService.getStatusVariablesList(variables);
+    assertEquals(2, resolvedList.size());
+    assertEquals("RESOLVED_VAR1", resolvedList.get(0));
+    assertEquals("RESOLVED_VAR2", resolvedList.get(1));
+  }
+}
diff --git 
a/plugins/misc/async/src/test/java/org/apache/hop/www/async/DefaultsTest.java 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/DefaultsTest.java
new file mode 100644
index 0000000000..67ed127786
--- /dev/null
+++ 
b/plugins/misc/async/src/test/java/org/apache/hop/www/async/DefaultsTest.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.async;
+
+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;
+
+/** Test class for Defaults */
+public class DefaultsTest {
+
+  @Test
+  public void testAsyncStatusGroup() {
+    assertNotNull(Defaults.ASYNC_STATUS_GROUP);
+    assertEquals("ASYNC_STATUS_GROUP", Defaults.ASYNC_STATUS_GROUP);
+  }
+
+  @Test
+  public void testAsyncActionPipelineServiceName() {
+    assertNotNull(Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME);
+    assertEquals(
+        "enable-asynchronous-pipeline-service-name", 
Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME);
+  }
+
+  @Test
+  public void testConstantsAreNotBlank() {
+    assertNotNull(Defaults.ASYNC_STATUS_GROUP);
+    assertNotNull(Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME);
+
+    assertTrue(!Defaults.ASYNC_STATUS_GROUP.trim().isEmpty());
+    assertTrue(!Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME.trim().isEmpty());
+  }
+
+  @Test
+  public void testConstantsAreDifferent() {
+    
assertTrue(!Defaults.ASYNC_STATUS_GROUP.equals(Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME));
+  }
+
+  @Test
+  public void testConstantsFormat() {
+    assertTrue(Defaults.ASYNC_STATUS_GROUP.contains("ASYNC"));
+    assertTrue(Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME.contains("async"));
+
+    assertTrue(Defaults.ASYNC_STATUS_GROUP.contains("STATUS"));
+    
assertTrue(Defaults.ASYNC_ACTION_PIPELINE_SERVICE_NAME.contains("pipeline"));
+  }
+}
diff --git 
a/ui/src/main/java/org/apache/hop/ui/www/service/WebServiceEditor.java 
b/ui/src/main/java/org/apache/hop/ui/www/service/WebServiceEditor.java
index 31eb27e6c5..6b3a124859 100644
--- a/ui/src/main/java/org/apache/hop/ui/www/service/WebServiceEditor.java
+++ b/ui/src/main/java/org/apache/hop/ui/www/service/WebServiceEditor.java
@@ -189,10 +189,8 @@ public class WebServiceEditor extends 
MetadataEditor<WebService> {
             PipelineRunConfiguration.class,
             parent,
             SWT.NONE,
-            "Pipeline run configuration",
-            "This is the pipeline run configuration to use on the server. "
-                + "If left blank a standard local pipeline engine is used. "
-                + "To return values please use a local pipeline engine.");
+            BaseMessages.getString(PKG, 
"WebServiceEditor.Runconfiguration.Label"),
+            BaseMessages.getString(PKG, 
"WebServiceEditor.Runconfiguration.Tooltip"));
     FormData fdRunConfiguration = new FormData();
     fdRunConfiguration.left = new FormAttachment(0, 0);
     fdRunConfiguration.top = new FormAttachment(lastControl, margin);
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/www/service/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/www/service/messages/messages_en_US.properties
index 7c8cb4612e..ddc4a326a0 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/www/service/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/www/service/messages/messages_en_US.properties
@@ -39,3 +39,5 @@ WebserviceGuiPlugin.GuiAction.SelectOutputField.Label=Select 
the output field
 WebserviceGuiPlugin.GuiAction.SelectService.Description=Select the web service 
to update
 WebserviceGuiPlugin.GuiAction.SelectService.Label=Select a web service
 WebserviceGuiPlugin.GuiAction.ToolTip=Use the output of this transform as a 
web service with Hop Server
+WebServiceEditor.Runconfiguration.Label=Pipeline run configuration
+WebServiceEditor.Runconfiguration.Tooltip=This is the pipeline run 
configuration to use on the server. If left blank a standard local pipeline 
engine is used. To return values please use a local pipeline engine.
\ No newline at end of file


Reply via email to