This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 4_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit b8124a25e5e137f44216b05911f79c2e81f90624
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri Mar 27 15:07:34 2026 +0100

    [SYNCOPE-1945] A single WebSocketBejavior per Page
---
 .../client/console/topology/TabularTopology.java   |   3 -
 .../syncope/client/console/topology/Topology.java  |  16 ++-
 .../topology/TopologyWebSocketBehavior.java        |  48 +++----
 .../client/console/SyncopeConsoleSession.java      |   4 +-
 .../client/console/chartjs/ChartJSPanel.java       |   4 +-
 .../syncope/client/console/pages/BasePage.java     |  12 +-
 .../syncope/client/console/pages/Dashboard.java    |   2 +-
 .../console/panels/DashboardOverviewPanel.java     |  11 +-
 .../console/reports/ReportDirectoryPanel.java      |  15 +--
 .../tasks/ProvisioningTaskDirectoryPanel.java      |  14 ---
 .../console/tasks/SchedTaskDirectoryPanel.java     |  10 +-
 .../wicket/ws/BasePageWebSocketBehavior.java       | 139 +++++++++++++++++++++
 .../wicket/ws/RefreshWebSocketBehavior.java        |  85 -------------
 .../client/console/widgets/ExtAlertWidget.java     |  10 +-
 .../syncope/client/console/widgets/JobWidget.java  |  10 +-
 pom.xml                                            |   2 +-
 16 files changed, 221 insertions(+), 164 deletions(-)

diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
index 94b99b07cc..5e4960478e 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
@@ -39,9 +39,6 @@ public class TabularTopology extends BasePage {
     private static final long serialVersionUID = -4434385801124981824L;
 
     public TabularTopology() {
-        TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior();
-        body.add(websocket);
-
         WebMarkupContainer content = new WebMarkupContainer("content");
         body.add(content.setOutputMarkupId(true));
 
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
index a7cbcbf759..8a42ee6dbf 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
@@ -46,6 +46,8 @@ import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import 
org.apache.syncope.client.console.wizards.resources.AbstractResourceWizardBuilder.CreateEvent;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.annotations.IdMPage;
+import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.types.IdMEntitlement;
@@ -71,6 +73,12 @@ public class Topology extends BasePage {
 
     public static final String CONNECTOR_SERVER_LOCATION_PREFIX = "connid://";
 
+    @SpringBean
+    protected ServiceOps serviceOps;
+
+    @SpringBean
+    protected ConfParamOps confParamOps;
+
     @SpringBean
     protected ResourceRestClient resourceRestClient;
 
@@ -158,8 +166,9 @@ public class Topology extends BasePage {
         body.add(modal.size(Modal.Size.Large));
         modal.setWindowClosedCallback(target -> modal.show(false));
 
-        TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior();
-        body.add(websocket);
+        TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior(
+                serviceOps, confParamOps, connectorRestClient, 
resourceRestClient);
+        webSocketBehavior.add(websocket);
 
         togglePanel = new TopologyTogglePanel("toggle", getPageReference());
         body.add(togglePanel);
@@ -574,8 +583,7 @@ public class Topology extends BasePage {
 
             payload.getTarget().appendJavaScript(String.format(
                     "window.Wicket.WebSocket.send('"
-                    + 
"{\"kind\":\"%s\",\"target\":\"%s\",\"source\":\"%s\",\"scope\":\"%s\"}"
-                    + "');",
+                    + 
"{\"kind\":\"%s\",\"target\":\"%s\",\"source\":\"%s\",\"scope\":\"%s\"}');",
                     SupportedOperation.ADD_ENDPOINT,
                     payload.getKey(),
                     payload.getParent(),
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java
index 1c81d023b6..5004f4d4e3 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyWebSocketBehavior.java
@@ -34,18 +34,16 @@ import java.util.concurrent.TimeoutException;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.rest.ConnectorRestClient;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
-import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
 import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 import org.apache.wicket.protocol.ws.api.message.TextMessage;
-import org.apache.wicket.spring.injection.annot.SpringBean;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
 
-public class TopologyWebSocketBehavior extends WebSocketBehavior {
+public class TopologyWebSocketBehavior extends 
BasePageWebSocketBehavior.OnMessageChild {
 
     private static final long serialVersionUID = -1653665542635275551L;
 
@@ -57,17 +55,13 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
 
     protected static final String RESOURCE_TEST_TIMEOUT_PARAMETER = 
"resource.test.timeout";
 
-    @SpringBean
-    protected ServiceOps serviceOps;
+    protected final ServiceOps serviceOps;
 
-    @SpringBean
-    protected ConfParamOps confParamOps;
+    protected final ConfParamOps confParamOps;
 
-    @SpringBean
-    protected ConnectorRestClient connectorRestClient;
+    protected final ConnectorRestClient connectorRestClient;
 
-    @SpringBean
-    protected ResourceRestClient resourceRestClient;
+    protected final ResourceRestClient resourceRestClient;
 
     protected final Map<String, String> connectors = 
Collections.synchronizedMap(new HashMap<>());
 
@@ -77,8 +71,6 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
 
     protected final Set<String> runningResCheck = 
Collections.synchronizedSet(new HashSet<>());
 
-    protected final transient SimpleAsyncTaskExecutor executor = new 
SimpleAsyncTaskExecutor();
-
     protected String coreAddress;
 
     protected String domain;
@@ -89,8 +81,16 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
 
     protected Integer resourceTestTimeout = null;
 
-    public TopologyWebSocketBehavior() {
-        executor.setVirtualThreads(true);
+    public TopologyWebSocketBehavior(
+            final ServiceOps serviceOps,
+            final ConfParamOps confParamOps,
+            final ConnectorRestClient connectorRestClient,
+            final ResourceRestClient resourceRestClient) {
+
+        this.serviceOps = serviceOps;
+        this.confParamOps = confParamOps;
+        this.connectorRestClient = connectorRestClient;
+        this.resourceRestClient = resourceRestClient;
 
         coreAddress = serviceOps.get(NetworkService.Type.CORE).getAddress();
         domain = SyncopeConsoleSession.get().getDomain();
@@ -119,7 +119,7 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
                 response = checker.call();
             } else {
                 LOG.debug("Timeouts provided for resource connection checking 
... ");
-                response = executor.submit(checker).get(timeout, 
TimeUnit.SECONDS);
+                response = 
SyncopeConsoleSession.get().execute(checker).get(timeout, TimeUnit.SECONDS);
             }
         } catch (InterruptedException | TimeoutException e) {
             LOG.warn("Connection with {} timed out", checker.key);
@@ -141,7 +141,7 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
         try {
             JsonNode obj = MAPPER.readTree(message.getText());
             switch 
(Topology.SupportedOperation.valueOf(obj.get("kind").asText())) {
-                case CHECK_CONNECTOR:
+                case CHECK_CONNECTOR -> {
                     String ckey = obj.get("target").asText();
 
                     if (connectors.containsKey(ckey)) {
@@ -163,9 +163,9 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
                             LOG.error("Unexpected error", e);
                         }
                     }
-                    break;
+                }
 
-                case CHECK_RESOURCE:
+                case CHECK_RESOURCE -> {
                     String rkey = obj.get("target").asText();
 
                     if (resources.containsKey(rkey)) {
@@ -187,16 +187,16 @@ public class TopologyWebSocketBehavior extends 
WebSocketBehavior {
                             LOG.error("Unexpected error", e);
                         }
                     }
-                    break;
+                }
 
-                case ADD_ENDPOINT:
+                case ADD_ENDPOINT ->
                     handler.appendJavaScript(String.format("addEndpoint('%s', 
'%s', '%s');",
                             obj.get("source").asText(),
                             obj.get("target").asText(),
                             obj.get("scope").asText()));
-                    break;
 
-                default:
+                default -> {
+                }
             }
         } catch (IOException e) {
             LOG.error("Error managing websocket message", e);
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index 72aeb6977a..7f3b8ddf48 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -64,7 +64,6 @@ import 
org.apache.wicket.authroles.authorization.strategies.role.Roles;
 import org.apache.wicket.request.Request;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
 import org.springframework.core.task.TaskRejectedException;
 import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
 import org.springframework.util.CollectionUtils;
@@ -106,7 +105,7 @@ public class SyncopeConsoleSession extends 
AuthenticatedWebSession implements Ba
 
     protected final Map<Class<?>, Object> services = 
Collections.synchronizedMap(new HashMap<>());
 
-    protected final SimpleAsyncTaskExecutor executor;
+    protected final SimpleAsyncTaskScheduler executor = new 
SimpleAsyncTaskScheduler();
 
     protected String domain;
 
@@ -133,7 +132,6 @@ public class SyncopeConsoleSession extends 
AuthenticatedWebSession implements Ba
 
         clientFactory = SyncopeWebApplication.get().newClientFactory();
 
-        executor = new SimpleAsyncTaskScheduler();
         executor.setVirtualThreads(true);
     }
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/chartjs/ChartJSPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/chartjs/ChartJSPanel.java
index 5781084802..191f37f696 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/chartjs/ChartJSPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/chartjs/ChartJSPanel.java
@@ -18,7 +18,7 @@
  */
 package org.apache.syncope.client.console.chartjs;
 
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.json.JsonMapper;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -34,7 +34,7 @@ public class ChartJSPanel extends Panel {
     private static final Logger LOG = 
LoggerFactory.getLogger(ChartJSPanel.class);
 
     private static final JsonMapper MAPPER = JsonMapper.builder().
-            
findAndAddModules().serializationInclusion(Include.NON_NULL).build();
+            
findAndAddModules().defaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL).build();
 
     private final IModel<? extends Chart<?>> model;
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index cc93e9196c..1498751090 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -33,7 +33,7 @@ import 
org.apache.syncope.client.console.panels.SessionExpirationModalPanel;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
 import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.annotations.AMPage;
@@ -92,6 +92,8 @@ public class BasePage extends BaseWebPage {
 
     protected final BaseModal<Serializable> sessionExpiration = new 
BaseModal<>("sessionExpirationModal");
 
+    protected final BasePageWebSocketBehavior webSocketBehavior = new 
BasePageWebSocketBehavior();
+
     public BasePage() {
         this(null);
     }
@@ -119,9 +121,11 @@ public class BasePage extends BaseWebPage {
         sessionExpiration.setWindowClosedCallback(target -> 
sessionExpiration.show(false));
         body.add(sessionExpiration);
 
-        add(new RefreshWebSocketBehavior() {
+        add(webSocketBehavior);
+        webSocketBehavior.add(new BasePageWebSocketBehavior.OnTimerChild(
+                SyncopeWebApplication.get().getJwtExpirationMinutesThreshold() 
- 1, TimeUnit.MINUTES) {
 
-            private static final long serialVersionUID = 8165980028271428241L;
+            private static final long serialVersionUID = 532119924423529449L;
 
             @Override
             protected void onTimer(final WebSocketRequestHandler handler) {
@@ -133,7 +137,7 @@ public class BasePage extends BaseWebPage {
                     handler.add(sessionExpiration);
                 }
             }
-        
}.schedule(SyncopeWebApplication.get().getJwtExpirationMinutesThreshold() - 1, 
TimeUnit.MINUTES));
+        });
 
         Serializable leftMenuCollapse = 
SyncopeConsoleSession.get().getAttribute(Constants.MENU_COLLAPSE);
         if ((leftMenuCollapse instanceof final Boolean b) && b) {
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
index 45b9fafcd9..06fda93a53 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
@@ -57,7 +57,7 @@ public class Dashboard extends BasePage {
 
             @Override
             public Panel getPanel(final String panelId) {
-                return new DashboardOverviewPanel(panelId);
+                return new DashboardOverviewPanel(panelId, getPageReference());
             }
         });
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
index 7d0aa7e89f..7be76656a7 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
@@ -20,13 +20,14 @@ package org.apache.syncope.client.console.panels;
 
 import java.util.concurrent.TimeUnit;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.AnyByRealmWidget;
 import org.apache.syncope.client.console.widgets.CompletenessWidget;
 import org.apache.syncope.client.console.widgets.LoadWidget;
 import org.apache.syncope.client.console.widgets.NumberWidget;
 import org.apache.syncope.client.console.widgets.UsersByStatusWidget;
 import org.apache.syncope.common.lib.info.NumbersInfo;
+import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.ResourceModel;
@@ -88,7 +89,7 @@ public class DashboardOverviewPanel extends Panel {
 
     private final LoadWidget load;
 
-    public DashboardOverviewPanel(final String id) {
+    public DashboardOverviewPanel(final String id, final PageReference 
pageRef) {
         super(id);
 
         NumbersInfo numbers = 
SyncopeConsoleSession.get().getAnonymousClient().numbers();
@@ -134,7 +135,9 @@ public class DashboardOverviewPanel extends Panel {
         load = new LoadWidget("load", 
SyncopeConsoleSession.get().getAnonymousClient().system());
         container.add(load);
 
-        container.add(new RefreshWebSocketBehavior() {
+        pageRef.getPage().getBehaviors().stream().
+                
filter(BasePageWebSocketBehavior.class::isInstance).map(BasePageWebSocketBehavior.class::cast).
+                findFirst().ifPresent(wsb -> wsb.add(new 
BasePageWebSocketBehavior.OnTimerChild(60, TimeUnit.SECONDS) {
 
             private static final long serialVersionUID = -7095269057058900157L;
 
@@ -180,6 +183,6 @@ public class DashboardOverviewPanel extends Panel {
                 
load.refresh(SyncopeConsoleSession.get().getAnonymousClient().system());
                 handler.add(load);
             }
-        }.schedule(60, TimeUnit.SECONDS));
+        }));
     }
 }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
index b4718e18cc..ab6a8afa86 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
@@ -40,7 +40,7 @@ import 
org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
@@ -99,7 +99,9 @@ public abstract class ReportDirectoryPanel
         modal.size(Modal.Size.Large);
         initResultTable();
 
-        container.add(new RefreshWebSocketBehavior() {
+        pageRef.getPage().getBehaviors().stream().
+                
filter(BasePageWebSocketBehavior.class::isInstance).map(BasePageWebSocketBehavior.class::cast).
+                findFirst().ifPresent(wsb -> wsb.add(new 
BasePageWebSocketBehavior.OnTimerChild(10, TimeUnit.SECONDS) {
 
             private static final long serialVersionUID = -4661303265651934868L;
 
@@ -108,7 +110,7 @@ public abstract class ReportDirectoryPanel
                 container.modelChanged();
                 handler.add(container);
             }
-        }.schedule(10, TimeUnit.SECONDS));
+        }));
 
         startAt = new ReportStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
@@ -221,8 +223,7 @@ public abstract class ReportDirectoryPanel
             public void onClick(final AjaxRequestTarget target, final ReportTO 
ignore) {
                 final ReportTO clone = 
SerializationUtils.clone(model.getObject());
                 clone.setKey(null);
-                send(ReportDirectoryPanel.this, Broadcast.EXACT,
-                        new AjaxWizard.EditItemActionEvent<>(clone, target));
+                send(ReportDirectoryPanel.this, Broadcast.EXACT, new 
AjaxWizard.EditItemActionEvent<>(clone, target));
             }
         }, ActionLink.ActionType.CLONE, IdRepoEntitlement.REPORT_CREATE);
 
@@ -232,8 +233,8 @@ public abstract class ReportDirectoryPanel
 
             @Override
             public void onClick(final AjaxRequestTarget target, final ReportTO 
ignore) {
-                startAt.setExecutionDetail(
-                        model.getObject().getKey(), 
model.getObject().getName(), target);
+                ReportDirectoryPanel.this.getTogglePanel().close(target);
+                startAt.setExecutionDetail(model.getObject().getKey(), 
model.getObject().getName(), target);
                 startAt.toggle(target, true);
             }
         }, ActionLink.ActionType.EXECUTE, IdRepoEntitlement.REPORT_EXECUTE);
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
index 11bcf5fe61..9ab7e8b95e 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
@@ -21,11 +21,9 @@ package org.apache.syncope.client.console.tasks;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.TaskRestClient;
 import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.common.lib.to.InboundTaskTO;
 import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
@@ -36,7 +34,6 @@ import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.model.StringResourceModel;
-import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 
 /**
  * Tasks page.
@@ -75,17 +72,6 @@ public abstract class ProvisioningTaskDirectoryPanel<T 
extends ProvisioningTaskT
         // super in order to call the parent implementation
         enableUtilityButton();
         super.initResultTable();
-
-        container.add(new RefreshWebSocketBehavior() {
-
-            private static final long serialVersionUID = -4661303265651934868L;
-
-            @Override
-            protected void onTimer(final WebSocketRequestHandler handler) {
-                container.modelChanged();
-                handler.add(container);
-            }
-        }.schedule(10, TimeUnit.SECONDS));
     }
 
     @Override
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index 76104b0d1b..d67afcecd6 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -41,7 +41,7 @@ import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
@@ -121,16 +121,18 @@ public abstract class SchedTaskDirectoryPanel<T extends 
SchedTaskTO>
 
         initResultTable();
 
-        container.add(new RefreshWebSocketBehavior() {
+        pageRef.getPage().getBehaviors().stream().
+                
filter(BasePageWebSocketBehavior.class::isInstance).map(BasePageWebSocketBehavior.class::cast).
+                findFirst().ifPresent(wsb -> wsb.add(new 
BasePageWebSocketBehavior.OnTimerChild(10, TimeUnit.SECONDS) {
 
-            private static final long serialVersionUID = -4661303265651934868L;
+            private static final long serialVersionUID = 532119924423529449L;
 
             @Override
             protected void onTimer(final WebSocketRequestHandler handler) {
                 container.modelChanged();
                 handler.add(container);
             }
-        }.schedule(10, TimeUnit.SECONDS));
+        }));
 
         startAt = new TaskStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/BasePageWebSocketBehavior.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/BasePageWebSocketBehavior.java
new file mode 100644
index 0000000000..99a0a5cfd6
--- /dev/null
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/BasePageWebSocketBehavior.java
@@ -0,0 +1,139 @@
+/*
+ * 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.syncope.client.console.wicket.ws;
+
+import com.giffing.wicket.spring.boot.starter.web.WicketWebInitializer;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
+import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.ClosedMessage;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+import org.apache.wicket.protocol.ws.api.message.TextMessage;
+import org.apache.wicket.protocol.ws.api.registry.IKey;
+
+public class BasePageWebSocketBehavior extends WebSocketBehavior {
+
+    private static final long serialVersionUID = -2572183403109560579L;
+
+    protected abstract static class Child implements Serializable {
+
+        private static final long serialVersionUID = -8242819397698846428L;
+
+        protected final String nonce = UUID.randomUUID().toString();
+
+    }
+
+    public abstract static class OnTimerChild extends Child {
+
+        private static final long serialVersionUID = -2368062250910082871L;
+
+        protected final long period;
+
+        protected final TimeUnit unit;
+
+        public OnTimerChild(final long period, final TimeUnit unit) {
+            this.period = period;
+            this.unit = unit;
+        }
+
+        protected abstract void onTimer(WebSocketRequestHandler handler);
+    }
+
+    public abstract static class OnMessageChild extends Child {
+
+        private static final long serialVersionUID = 2306119214592765956L;
+
+        protected abstract void onMessage(WebSocketRequestHandler handler, 
TextMessage message);
+    }
+
+    protected record WSConnectionInfo(String sessionId, IKey ikey) implements 
Serializable {
+
+    }
+
+    protected record RefreshMessage(String nonce) implements 
IWebSocketPushMessage {
+
+    }
+
+    protected final List<Child> children = new ArrayList<>();
+
+    protected WSConnectionInfo wsConnectionInfo;
+
+    public void add(final Child child) {
+        if (children.stream().noneMatch(c -> c.nonce.equals(child.nonce))) {
+            children.add(child);
+
+            if (child instanceof OnTimerChild onTimerChild && wsConnectionInfo 
!= null) {
+                schedule(onTimerChild, wsConnectionInfo.sessionId(), 
wsConnectionInfo.ikey());
+            }
+        }
+    }
+
+    protected void schedule(final OnTimerChild child, final String sessionId, 
final IKey ikey) {
+        ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(1, Thread.ofVirtual().factory());
+        executor.scheduleAtFixedRate(() -> {
+            Application application = 
Application.get(WicketWebInitializer.WICKET_FILTERNAME);
+            IWebSocketConnection wsConnection = 
WebSocketSettings.Holder.get(application).
+                    getConnectionRegistry().getConnection(application, 
sessionId, ikey);
+            
Optional.ofNullable(wsConnection).filter(IWebSocketConnection::isOpen).ifPresentOrElse(
+                    wsc -> wsc.sendMessage(new RefreshMessage(child.nonce)),
+                    () -> executor.shutdownNow());
+        }, 0, child.period, child.unit);
+    }
+
+    @Override
+    protected void onConnect(final ConnectedMessage message) {
+        wsConnectionInfo = new WSConnectionInfo(message.getSessionId(), 
message.getKey());
+
+        children.stream().
+                
filter(OnTimerChild.class::isInstance).map(OnTimerChild.class::cast).
+                forEach(child -> schedule(child, message.getSessionId(), 
message.getKey()));
+    }
+
+    @Override
+    protected void onMessage(final WebSocketRequestHandler handler, final 
TextMessage message) {
+        children.stream().
+                
filter(OnMessageChild.class::isInstance).map(OnMessageChild.class::cast).findFirst().
+                ifPresent(child -> child.onMessage(handler, message));
+    }
+
+    @Override
+    protected void onPush(final WebSocketRequestHandler handler, final 
IWebSocketPushMessage message) {
+        if (message instanceof RefreshMessage refreshMessage) {
+            children.stream().
+                    
filter(OnTimerChild.class::isInstance).map(OnTimerChild.class::cast).
+                    filter(child -> 
child.nonce.equals(refreshMessage.nonce())).findFirst().
+                    ifPresent(child -> child.onTimer(handler));
+        }
+    }
+
+    @Override
+    protected void onClose(final ClosedMessage message) {
+        children.clear();
+    }
+}
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java
deleted file mode 100644
index 9485212b34..0000000000
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.syncope.client.console.wicket.ws;
-
-import com.giffing.wicket.spring.boot.starter.web.WicketWebInitializer;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang3.mutable.Mutable;
-import org.apache.commons.lang3.mutable.MutableObject;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.wicket.Application;
-import org.apache.wicket.protocol.ws.WebSocketSettings;
-import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
-import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
-import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
-import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
-import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
-import org.apache.wicket.protocol.ws.api.registry.IKey;
-
-public abstract class RefreshWebSocketBehavior extends WebSocketBehavior {
-
-    private record RefreshMessage(String nonce) implements 
IWebSocketPushMessage {
-
-    }
-
-    private static final long serialVersionUID = 5636572627689425575L;
-
-    protected final Mutable<Pair<String, IKey>> websocketInfo = new 
MutableObject<>();
-
-    protected final String nonce;
-
-    public RefreshWebSocketBehavior() {
-        nonce = UUID.randomUUID().toString();
-    }
-
-    @Override
-    protected void onConnect(final ConnectedMessage message) {
-        websocketInfo.setValue(Pair.of(message.getSessionId(), 
message.getKey()));
-    }
-
-    protected abstract void onTimer(WebSocketRequestHandler handler);
-
-    @Override
-    protected void onPush(final WebSocketRequestHandler handler, final 
IWebSocketPushMessage message) {
-        if (message instanceof RefreshMessage refreshMessage && 
nonce.equals(refreshMessage.nonce())) {
-            onTimer(handler);
-        }
-    }
-
-    public RefreshWebSocketBehavior schedule(final long period, final TimeUnit 
unit) {
-        ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(1, Thread.ofVirtual().factory());
-        executor.scheduleAtFixedRate(() -> {
-            if (websocketInfo.get() == null) {
-                return;
-            }
-
-            Application application = 
Application.get(WicketWebInitializer.WICKET_FILTERNAME);
-            IWebSocketConnection wsConnection = 
WebSocketSettings.Holder.get(application).getConnectionRegistry().
-                    getConnection(application, websocketInfo.get().getLeft(), 
websocketInfo.get().getRight());
-            
Optional.ofNullable(wsConnection).filter(IWebSocketConnection::isOpen).ifPresentOrElse(
-                    wsc -> wsc.sendMessage(new RefreshMessage(nonce)),
-                    () -> executor.shutdownNow());
-        }, 0, period, unit);
-
-        return this;
-    }
-}
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
index d2fcd97485..20d1d65412 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
@@ -19,7 +19,7 @@
 package org.apache.syncope.client.console.widgets;
 
 import java.util.concurrent.TimeUnit;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 
@@ -33,9 +33,11 @@ public abstract class ExtAlertWidget extends AlertWidget {
         super(id);
         this.pageRef = pageRef;
 
-        add(new RefreshWebSocketBehavior() {
+        pageRef.getPage().getBehaviors().stream().
+                
filter(BasePageWebSocketBehavior.class::isInstance).map(BasePageWebSocketBehavior.class::cast).
+                findFirst().ifPresent(wsb -> wsb.add(new 
BasePageWebSocketBehavior.OnTimerChild(30, TimeUnit.SECONDS) {
 
-            private static final long serialVersionUID = -7095269057058900157L;
+            private static final long serialVersionUID = 532119924423529449L;
 
             @Override
             protected void onTimer(final WebSocketRequestHandler handler) {
@@ -48,6 +50,6 @@ public abstract class ExtAlertWidget extends AlertWidget {
                     handler.add(headerAlertsNumber);
                 }
             }
-        }.schedule(30, TimeUnit.SECONDS));
+        }));
     }
 }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index 635a45017f..7e666fac7f 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -49,7 +49,7 @@ import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
+import org.apache.syncope.client.console.wicket.ws.BasePageWebSocketBehavior;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
@@ -174,7 +174,9 @@ public class JobWidget extends BaseWidget {
         recent = getUpdatedRecent();
 
         container = new WebMarkupContainer("jobContainer");
-        container.add(new RefreshWebSocketBehavior() {
+        pageRef.getPage().getBehaviors().stream().
+                
filter(BasePageWebSocketBehavior.class::isInstance).map(BasePageWebSocketBehavior.class::cast).
+                findFirst().ifPresent(wsb -> wsb.add(new 
BasePageWebSocketBehavior.OnTimerChild(10, TimeUnit.SECONDS) {
 
             private static final long serialVersionUID = 7298597675929755960L;
 
@@ -200,7 +202,7 @@ public class JobWidget extends BaseWidget {
                     }
                 }
             }
-        }.schedule(10, TimeUnit.SECONDS));
+        }));
         add(container);
 
         container.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", 
buildTabList(pageRef)));
@@ -559,7 +561,7 @@ public class JobWidget extends BaseWidget {
         }
     }
 
-    private class RecentExecPanel
+    public class RecentExecPanel
             extends DirectoryPanel<ExecTO, ExecTO, 
RecentExecPanel.RecentExecProvider, BaseRestClient> {
 
         private static final long serialVersionUID = -8214546246301342868L;
diff --git a/pom.xml b/pom.xml
index 018ddaa274..2c6b4cd599 100644
--- a/pom.xml
+++ b/pom.xml
@@ -435,7 +435,7 @@ under the License.
     <bouncycastle.version>1.83</bouncycastle.version>
     <nimbus-jose-jwt.version>10.8</nimbus-jose-jwt.version>
 
-    <spring-boot.version>3.5.12</spring-boot.version>
+    <spring-boot.version>3.5.13</spring-boot.version>
     <spring-cloud-gateway.version>4.3.3</spring-cloud-gateway.version>
 
     <openjpa.version>4.1.1</openjpa.version>


Reply via email to