diff --git a/actions/pom.xml b/actions/pom.xml index 73487d32..bf87e72c 100644 --- a/actions/pom.xml +++ b/actions/pom.xml @@ -23,11 +23,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 tools.dynamia.actions - 5.4.9 + 5.4.10 DynamiaTools - Actions https://dynamia.tools/docs/actions @@ -65,12 +65,12 @@ tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 diff --git a/app/pom.xml b/app/pom.xml index 3db4b8dd..dd4c6603 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,11 +23,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 tools.dynamia.app - 5.4.9 + 5.4.10 DynamiaTools - App https://dynamia.tools/docs/app @@ -74,58 +74,58 @@ tools.dynamia tools.dynamia.actions - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.crud - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.domain - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.io - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.navigation - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.reports - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.templates - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.viewers - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.web - 5.4.9 + 5.4.10 org.springframework.data @@ -208,7 +208,7 @@ tools.dynamia tools.dynamia.domain.jpa - 5.4.9 + 5.4.10 test diff --git a/commons/pom.xml b/commons/pom.xml index 1e0fa8d9..b863c77c 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -22,11 +22,11 @@ tools.dynamia.commons jar - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Commons https://dynamia.tools/docs/common diff --git a/crud/pom.xml b/crud/pom.xml index 9943e0d6..f1e7a4d0 100644 --- a/crud/pom.xml +++ b/crud/pom.xml @@ -23,11 +23,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 tools.dynamia.crud - 5.4.9 + 5.4.10 DynamiaTools - CRUD https://dynamia.tools/docs/crud @@ -62,23 +62,23 @@ tools.dynamia tools.dynamia.actions - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.viewers - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.navigation - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.domain.jpa - 5.4.9 + 5.4.10 test diff --git a/domain-jpa/pom.xml b/domain-jpa/pom.xml index d66217d8..c337b910 100644 --- a/domain-jpa/pom.xml +++ b/domain-jpa/pom.xml @@ -23,13 +23,13 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Domain JPA https://dynamia.tools/docs/domain tools.dynamia.domain.jpa - 5.4.9 + 5.4.10 jar diff --git a/domain/pom.xml b/domain/pom.xml index cad08e8e..830d5095 100644 --- a/domain/pom.xml +++ b/domain/pom.xml @@ -21,13 +21,13 @@ 4.0.0 tools.dynamia.domain - 5.4.9 + 5.4.10 jar tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Domain https://dynamia.tools/docs/domain diff --git a/integration/pom.xml b/integration/pom.xml index 2e8e453a..e66b8b85 100644 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -27,12 +27,12 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Integration A set of classes and interfaces that help integrate modules - 5.4.9 + 5.4.10 https://dynamia.tools/docs/integration diff --git a/integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java b/integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java index 6c4223e5..2c5de048 100644 --- a/integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java +++ b/integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java @@ -307,14 +307,14 @@ public static boolean isSchedulingEnabled() { } /** - * Schedule a task with fixed delay using Spring TaskScheduler + * Schedule a task at fixed rate using Spring TaskScheduler for repeated execution. * - * @param delay the delay - * @param task the task + * @param rate the rate + * @param task the task to repeat each period * @return the scheduled future */ - public static ScheduledFuture scheduleWithFixedDelay(Duration delay, Runnable task) { - return getTaskScheduler().scheduleAtFixedRate(task, delay); + public static ScheduledFuture scheduleAtFixedRate(Duration rate, Runnable task) { + return getTaskScheduler().scheduleAtFixedRate(task, rate); } /** diff --git a/io/pom.xml b/io/pom.xml index fe48fb56..ac80a13f 100644 --- a/io/pom.xml +++ b/io/pom.xml @@ -28,11 +28,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - IO - 5.4.9 + 5.4.10 A set of classes and interfaces that help in any kind io task https://dynamia.tools/docs/io diff --git a/navigation/pom.xml b/navigation/pom.xml index 3bf25d84..96110c2f 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -23,11 +23,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 tools.dynamia.navigation - 5.4.9 + 5.4.10 DynamiaTools - Navigation https://dynamia.tools/docs/navigation @@ -63,17 +63,17 @@ tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.actions - 5.4.9 + 5.4.10 diff --git a/pom.xml b/pom.xml index eef41758..4b393f73 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 4.0.0 tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 pom Dynamia Soluciones IT SAS diff --git a/reports/pom.xml b/reports/pom.xml index cb33e98c..b6862dd7 100644 --- a/reports/pom.xml +++ b/reports/pom.xml @@ -26,11 +26,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Reports - 5.4.9 + 5.4.10 A set of classes and interfaces that help building Reports https://dynamia.tools/docs/reports diff --git a/starter/pom.xml b/starter/pom.xml index 50e9251d..b1809e6a 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -4,7 +4,7 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 dynamia-tools-starter DynamiaTools - Starter @@ -26,17 +26,17 @@ tools.dynamia tools.dynamia.app - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.zk - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.domain.jpa - 5.4.9 + 5.4.10 org.hibernate.validator diff --git a/templates/pom.xml b/templates/pom.xml index 91aaa5b2..19caddc3 100644 --- a/templates/pom.xml +++ b/templates/pom.xml @@ -23,12 +23,12 @@ tools.dynamia.parent tools.dynamia - 5.4.9 + 5.4.10 tools.dynamia.templates - 5.4.9 + 5.4.10 DynamiaTools - Templates https://dynamia.tools/docs/templates @@ -64,12 +64,12 @@ tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 diff --git a/ui/pom.xml b/ui/pom.xml index 07b683ac..90ccb70e 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -23,11 +23,11 @@ tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 tools.dynamia.ui - 5.4.9 + 5.4.10 DynamiaTools - UI https://dynamia.tools/docs/ui Helper classes for module integrations and messages @@ -64,17 +64,17 @@ tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.io - 5.4.9 + 5.4.10 diff --git a/viewers/pom.xml b/viewers/pom.xml index 2c0b164a..898b0228 100644 --- a/viewers/pom.xml +++ b/viewers/pom.xml @@ -23,13 +23,13 @@ tools.dynamia.viewers jar - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Viewers @@ -67,27 +67,27 @@ tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.io - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.domain - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.actions - 5.4.9 + 5.4.10 org.yaml diff --git a/web/pom.xml b/web/pom.xml index be5e838e..f298ba66 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -23,14 +23,14 @@ tools.dynamia.web jar - 5.4.9 + 5.4.10 A set of common classes and interfaces for web application development tools.dynamia tools.dynamia.parent - 5.4.9 + 5.4.10 DynamiaTools - Web @@ -88,27 +88,27 @@ tools.dynamia tools.dynamia.commons - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.integration - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.navigation - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.viewers - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.crud - 5.4.9 + 5.4.10 org.springframework diff --git a/zk/pom.xml b/zk/pom.xml index 314a1825..3fb59f59 100644 --- a/zk/pom.xml +++ b/zk/pom.xml @@ -21,12 +21,12 @@ tools.dynamia.parent tools.dynamia - 5.4.9 + 5.4.10 4.0.0 tools.dynamia.zk - 5.4.9 + 5.4.10 jar DynamiaTools - ZK https://dynamia.tools/docs/zk @@ -99,31 +99,31 @@ tools.dynamia tools.dynamia.web - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.navigation - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.ui - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.domain - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.viewers - 5.4.9 + 5.4.10 org.yaml @@ -134,19 +134,19 @@ tools.dynamia tools.dynamia.crud - 5.4.9 + 5.4.10 tools.dynamia tools.dynamia.reports - 5.4.9 + 5.4.10 compile tools.dynamia tools.dynamia.templates - 5.4.9 + 5.4.10 compile diff --git a/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java b/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java index 9146f8ab..3aa34f52 100644 --- a/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java +++ b/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java @@ -24,7 +24,7 @@ import org.zkoss.zk.ui.Desktop; import tools.dynamia.commons.logger.Loggable; -import java.util.Collection; +import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -62,8 +62,8 @@ */ public class WebSocketGlobalCommandHandler extends TextWebSocketHandler implements Loggable { - private final Map desktops = new ConcurrentHashMap<>(); - private final Map sessions = new ConcurrentHashMap<>(); + // Map of active WebSocket sessions by session ID + private final Map sessions = new ConcurrentHashMap<>(); /** * Handles incoming text messages from WebSocket clients. @@ -91,17 +91,18 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) return; } + if ("PONG".equals(payload)) { + // Ignore PONG messages + return; + } + // Handle desktop ID registration - String desktopId = payload; - String oldSessionId = desktops.get(desktopId); - if (oldSessionId != null) { - WebSocketSession oldSession = sessions.get(oldSessionId); - if (oldSession != null) { - oldSession.close(CloseStatus.NORMAL); - } + var oldSession = sessions.get(payload); + if (oldSession != null) { + oldSession.close(CloseStatus.NORMAL); } - log("Associating desktop " + desktopId + " with ws session " + session.getId()); - desktops.put(desktopId, session.getId()); + log("Associating desktop " + payload + " with ws session " + session.getId()); + sessions.put(session.getId(), new DeskstopWebSocketSession(payload, session)); } /** @@ -114,8 +115,7 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) */ @Override public void afterConnectionEstablished(WebSocketSession session) { - log("WebSocket connection established: " + session.getId()); - sessions.put(session.getId(), session); + log("New webSocket connection established: " + session.getId() + " waiting for desktop ID..."); } /** @@ -125,16 +125,11 @@ public void afterConnectionEstablished(WebSocketSession session) { * the internal maps. This ensures that closed connections don't accumulate in memory.

* * @param session the closed WebSocket session - * @param status the status code indicating why the connection was closed + * @param status the status code indicating why the connection was closed */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { log("WebSocket connection closed: " + session.getId() + " with status " + status); - - String desktopId = desktops.entrySet().stream().filter(e -> e.getValue().equals(session.getId())).map(Map.Entry::getKey).findFirst().orElse(null); - if (desktopId != null) { - desktops.remove(desktopId); - } sessions.remove(session.getId()); } @@ -147,15 +142,11 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) * @param desktop the ZK Desktop to find the session for * @return the associated WebSocket session, or {@code null} if not found or desktop is null */ - WebSocketSession findSession(Desktop desktop) { - WebSocketSession session = null; - if (desktop != null && desktop.getId() != null) { - String sessionID = desktops.get(desktop.getId()); - if (sessionID != null) { - session = sessions.get(sessionID); - } - } - return session; + public DeskstopWebSocketSession findSession(Desktop desktop) { + return sessions.values().stream() + .filter(ds -> ds.matchesDesktop(desktop)) + .findFirst() + .orElse(null); } /** @@ -166,7 +157,61 @@ WebSocketSession findSession(Desktop desktop) { * * @return a collection of all active WebSocket sessions */ - Collection getAllSessions() { - return sessions.values(); + public Map getSessions() { + return sessions; + } + + /** + * Closes the WebSocket session associated with a specific ZK Desktop. + * + *

This method safely closes the session and removes it from the internal + * session map to prevent resource leaks.

+ * + * @param desktop the ZK Desktop whose session should be closed + */ + public void closeSession(Desktop desktop) { + var session = findSession(desktop); + if (session != null) { + try { + session.close(CloseStatus.NORMAL); + } finally { + sessions.remove(session.session.getId()); + } + } else { + log("No websocket session found for desktop " + desktop.getId()); + } + } + + + /** + * Represents a WebSocket session associated with a specific ZK Desktop. + * + *

This record encapsulates the desktop ID and the WebSocket session, + * providing utility methods for session management and message sending.

+ */ + public record DeskstopWebSocketSession(String desktopId, WebSocketSession session) { + boolean isOpen() { + return session.isOpen(); + } + + boolean matchesDesktop(Desktop desktop) { + return desktop != null && desktopId.equals(desktop.getId()); + } + + boolean matchesSession(WebSocketSession otherSession) { + return session.getId().equals(otherSession.getId()); + } + + void close(CloseStatus closeStatus) { + try { + session.close(closeStatus); + } catch (Exception e) { + //ignore + } + } + + void sendMessage(String message) throws IOException { + session.sendMessage(new TextMessage(message)); + } } } diff --git a/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java b/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java index 93080fe6..f1c5df5a 100644 --- a/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java +++ b/zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java @@ -17,6 +17,7 @@ package tools.dynamia.zk.websocket; +import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.zkoss.zk.ui.Desktop; @@ -31,6 +32,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** * Helper class to send push command to desktop client using web socket @@ -66,21 +68,23 @@ public static boolean sendPushCommand(Desktop desktop, String command, Map { + AtomicInteger count = new AtomicInteger(); + handler.getSessions().values().forEach(s -> { try { - s.sendMessage(new TextMessage(command)); + s.sendMessage(command); + count.incrementAndGet(); } catch (IOException e) { - LOGGER.error("Error sending command " + command + " to WS Session: " + s); + LOGGER.error("IO Error sending command " + command + " to WS Session: " + s, e); + s.close(CloseStatus.NORMAL); + } catch (Exception e) { + LOGGER.error("Error sending command " + command + " to WS Session: " + s, e); } }); + LOGGER.info("Broadcasted command '" + command + "' to " + count.get() + " WS sessions."); } } - private static WebSocketGlobalCommandHandler getHandler() { + /** + * Broadcast a heartbeat PING command to all connected sessions + */ + public static void broadcastHeartbeat() { + broadcastCommand("PING"); + } + + + public static WebSocketGlobalCommandHandler getHandler() { return Containers.get().findObject(WebSocketGlobalCommandHandler.class); } diff --git a/zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js b/zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js index 8838a09f..ebde1ba4 100644 --- a/zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js +++ b/zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js @@ -118,7 +118,7 @@ // Keep-alive configuration var keepAliveConfig = { enabled: true, - interval: 30000 // Send ping every 30 seconds + interval: 60000 // Send ping every 60 seconds }; var wsManager = { @@ -301,6 +301,17 @@ return; } + if (e.data === 'PING') { + // Respond to server ping + try { + wsManager.socket.send('PONG'); + console.debug('DynamiaTools WebSocket: pong sent in response to server ping'); + } catch (error) { + console.warn('DynamiaTools WebSocket: error sending pong', error); + } + return; + } + console.debug('DynamiaTools command received:', e.data); try {