From c992ebac9d9bb5addcafd0972b774e91c220bd9f Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 28 Nov 2023 18:25:37 +0200 Subject: [PATCH 01/14] Use single WebSocket session --- .../service/ws/DefaultWebSocketService.java | 191 ++++++++---------- .../service/ws/WebSocketSessionType.java | 4 +- .../cmd/NotificationCmdsWrapper.java | 17 ++ ...ginCmdsWrapper.java => WsCmdsWrapper.java} | 17 +- .../telemetry/cmd/v1/TelemetryPluginCmd.java | 4 +- .../service/ws/telemetry/cmd/v2/DataCmd.java | 3 +- .../ws/telemetry/cmd/v2/UnsubscribeCmd.java | 4 +- .../controller/TbTestWebSocketClient.java | 10 +- .../notification/NotificationApiWsClient.java | 22 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 4 +- 10 files changed, 142 insertions(+), 134 deletions(-) rename application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/{TelemetryPluginCmdsWrapper.java => WsCmdsWrapper.java} (74%) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index d0334f90b6..47478b3fdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -66,7 +66,7 @@ import org.thingsboard.server.service.subscription.TbTimeSeriesSubscription; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.WsCmd; -import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; @@ -149,8 +149,7 @@ public class DefaultWebSocketService implements WebSocketService { private ScheduledExecutorService pingExecutor; private String serviceId; - private List> telemetryCmdsHandlers; - private List> notificationCmdsHandlers; + private List> cmdsHandlers; @PostConstruct public void init() { @@ -160,25 +159,23 @@ public class DefaultWebSocketService implements WebSocketService { pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping")); pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); - telemetryCmdsHandlers = List.of( - newCmdsHandler(TelemetryPluginCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd) - ); - notificationCmdsHandlers = List.of( - newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), - newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), - newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), - newCmdHandler(NotificationCmdsWrapper::getMarkAllAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd), - newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) + cmdsHandlers = List.of( + newCmdsHandler(WsCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), + newCmdsHandler(WsCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), + newCmdsHandler(WsCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), + newCmdsHandler(WsCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), + newCmdsHandler(WsCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), + newCmdsHandler(WsCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), + newCmdsHandler(WsCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), + newCmdsHandler(WsCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdHandler(WsCmdsWrapper::getUnreadNotificationsSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + newCmdHandler(WsCmdsWrapper::getUnreadNotificationsCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + newCmdHandler(WsCmdsWrapper::getMarkNotificationAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdHandler(WsCmdsWrapper::getMarkAllNotificationsAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd), + newCmdHandler(WsCmdsWrapper::getNotificationsUnsubCmd, notificationCmdsHandler::handleUnsubCmd) ); } @@ -221,8 +218,8 @@ public class DefaultWebSocketService implements WebSocketService { try { switch (sessionRef.getSessionType()) { - case TELEMETRY: - processTelemetryCmds(sessionRef, msg); + case GENERAL: + processCmds(sessionRef, msg); break; case NOTIFICATIONS: processNotificationCmds(sessionRef, msg); @@ -234,25 +231,27 @@ public class DefaultWebSocketService implements WebSocketService { } } - private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { - TelemetryPluginCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, TelemetryPluginCmdsWrapper.class); - if (cmdsWrapper == null) { - return; - } - for (WsCmdListHandler cmdHandler : telemetryCmdsHandlers) { - List cmds = cmdHandler.extractCmds(cmdsWrapper); - if (cmds != null) { - cmdHandler.handle(sessionRef, cmds); - } - } + private void processCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { + WsCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, WsCmdsWrapper.class); + processCmds(sessionRef, cmdsWrapper); } private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException { NotificationCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class); - for (WsCmdHandler cmdHandler : notificationCmdsHandlers) { - WsCmd cmd = cmdHandler.extractCmd(cmdsWrapper); - if (cmd != null) { - String sessionId = sessionRef.getSessionId(); + processCmds(sessionRef, cmdsWrapper.toCommonCmdsWrapper()); + } + + private void processCmds(WebSocketSessionRef sessionRef, WsCmdsWrapper cmdsWrapper) { + if (cmdsWrapper == null) { + return; + } + String sessionId = sessionRef.getSessionId(); + for (WsCmdsHandler cmdHandler : cmdsHandlers) { + List cmds = cmdHandler.extract(cmdsWrapper); + if (cmds == null) { + continue; + } + for (WsCmd cmd : cmds) { if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { try { cmdHandler.handle(sessionRef, cmd); @@ -269,8 +268,7 @@ public class DefaultWebSocketService implements WebSocketService { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) - && validateSubscriptionCmd(sessionRef, cmd)) { + if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } @@ -279,8 +277,7 @@ public class DefaultWebSocketService implements WebSocketService { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) - && validateSubscriptionCmd(sessionRef, cmd)) { + if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } @@ -289,8 +286,7 @@ public class DefaultWebSocketService implements WebSocketService { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) - && validateSubscriptionCmd(sessionRef, cmd)) { + if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } @@ -298,18 +294,14 @@ public class DefaultWebSocketService implements WebSocketService { private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd); - } + entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd); } private void handleWsAlarmCountCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId) - && validateSubscriptionCmd(sessionRef, cmd)) { + if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } @@ -457,19 +449,17 @@ public class DefaultWebSocketService implements WebSocketService { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd, sessionId)) { - if (cmd.isUnsubscribe()) { - unsubscribe(sessionRef, cmd, sessionId); - } else if (validateSubscriptionCmd(sessionRef, cmd)) { - EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); - log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId); - Optional> keysOptional = getKeys(cmd); - if (keysOptional.isPresent()) { - List keys = new ArrayList<>(keysOptional.get()); - handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys); - } else { - handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId); - } + if (cmd.isUnsubscribe()) { + unsubscribe(sessionRef, cmd, sessionId); + } else if (validateSubscriptionCmd(sessionRef, cmd)) { + EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); + log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId); + Optional> keysOptional = getKeys(cmd); + if (keysOptional.isPresent()) { + List keys = new ArrayList<>(keysOptional.get()); + handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys); + } else { + handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId); } } } @@ -511,7 +501,7 @@ public class DefaultWebSocketService implements WebSocketService { .build(); subLock.lock(); - try{ + try { oldSubService.addSubscription(sub); sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); } finally { @@ -543,15 +533,6 @@ public class DefaultWebSocketService implements WebSocketService { } private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) { - String sessionId = sessionRef.getSessionId(); - WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); - if (sessionMD == null) { - log.warn("[{}] Session meta data not found. ", sessionId); - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, - SESSION_META_DATA_NOT_FOUND); - sendWsMsg(sessionRef, update); - return; - } if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Device id is empty!"); @@ -662,18 +643,16 @@ public class DefaultWebSocketService implements WebSocketService { String sessionId = sessionRef.getSessionId(); log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSessionMetadata(sessionRef, cmd, sessionId)) { - if (cmd.isUnsubscribe()) { - unsubscribe(sessionRef, cmd, sessionId); - } else if (validateSubscriptionCmd(sessionRef, cmd)) { - EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); - Optional> keysOptional = getKeys(cmd); + if (cmd.isUnsubscribe()) { + unsubscribe(sessionRef, cmd, sessionId); + } else if (validateSubscriptionCmd(sessionRef, cmd)) { + EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); + Optional> keysOptional = getKeys(cmd); - if (keysOptional.isPresent()) { - handleWsTimeSeriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId); - } else { - handleWsTimeSeriesSubscription(sessionRef, cmd, sessionId, entityId); - } + if (keysOptional.isPresent()) { + handleWsTimeSeriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId); + } else { + handleWsTimeSeriesSubscription(sessionRef, cmd, sessionId, entityId); } } } @@ -787,7 +766,7 @@ public class DefaultWebSocketService implements WebSocketService { .build(); subLock.lock(); - try{ + try { oldSubService.addSubscription(sub); sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); } finally { @@ -1056,23 +1035,23 @@ public class DefaultWebSocketService implements WebSocketService { } - public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor, - BiConsumer handler) { + public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor, + BiConsumer handler) { return new WsCmdHandler<>(cmdExtractor, handler); } - public static WsCmdListHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, - BiConsumer handler) { - return new WsCmdListHandler<>(cmdsExtractor, handler); + public static WsCmdsHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, + BiConsumer handler) { + return new WsCmdsHandler<>(cmdsExtractor, handler); } @RequiredArgsConstructor - public static class WsCmdHandler { - private final java.util.function.Function cmdExtractor; - private final BiConsumer handler; + public static class WsCmdsHandler { + private final java.util.function.Function> cmdsExtractor; + protected final BiConsumer handler; - public C extractCmd(W cmdsWrapper) { - return cmdExtractor.apply(cmdsWrapper); + public List extract(WsCmdsWrapper cmdsWrapper) { + return cmdsExtractor.apply(cmdsWrapper); } @SuppressWarnings("unchecked") @@ -1081,20 +1060,12 @@ public class DefaultWebSocketService implements WebSocketService { } } - @RequiredArgsConstructor - public static class WsCmdListHandler { - private final java.util.function.Function> cmdsExtractor; - private final BiConsumer handler; - - public List extractCmds(W cmdsWrapper) { - return cmdsExtractor.apply(cmdsWrapper); - } - - @SuppressWarnings("unchecked") - public void handle(WebSocketSessionRef sessionRef, List cmds) { - cmds.forEach(cmd -> { - handler.accept(sessionRef, (C) cmd); - }); + public static class WsCmdHandler extends WsCmdsHandler { + public WsCmdHandler(java.util.function.Function cmdExtractor, BiConsumer handler) { + super(cmdsWrapper -> { + C cmd = cmdExtractor.apply(cmdsWrapper); + return cmd != null ? List.of(cmd) : null; + }, handler); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java index 5c1a155c4b..5547ef3359 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java @@ -24,8 +24,8 @@ import java.util.Optional; @RequiredArgsConstructor @Getter public enum WebSocketSessionType { - TELEMETRY("telemetry"), - NOTIFICATIONS("notifications"); + GENERAL("telemetry"), + NOTIFICATIONS("notifications"); // deprecated private final String name; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 88613eb966..de7a6adab7 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -15,9 +15,15 @@ */ package org.thingsboard.server.service.ws.notification.cmd; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +/** + * @deprecated Use {@link org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper}. This class is left for backward compatibility + * */ @Data +@Deprecated public class NotificationCmdsWrapper { private NotificationsCountSubCmd unreadCountSubCmd; @@ -30,4 +36,15 @@ public class NotificationCmdsWrapper { private NotificationsUnsubCmd unsubCmd; + @JsonIgnore + public WsCmdsWrapper toCommonCmdsWrapper() { + WsCmdsWrapper wrapper = new WsCmdsWrapper(); + wrapper.setUnreadNotificationsCountSubCmd(unreadCountSubCmd); + wrapper.setUnreadNotificationsSubCmd(unreadSubCmd); + wrapper.setMarkNotificationAsReadCmd(markAsReadCmd); + wrapper.setMarkAllNotificationsAsReadCmd(markAllAsReadCmd); + wrapper.setNotificationsUnsubCmd(unsubCmd); + return wrapper; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java similarity index 74% rename from application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java index 5b2c12afcd..40eb672030 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java @@ -16,6 +16,11 @@ package org.thingsboard.server.service.ws.telemetry.cmd; import lombok.Data; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; @@ -34,7 +39,7 @@ import java.util.List; * @author Andrew Shvayka */ @Data -public class TelemetryPluginCmdsWrapper { +public class WsCmdsWrapper { private List attrSubCmds; @@ -58,4 +63,14 @@ public class TelemetryPluginCmdsWrapper { private List alarmCountUnsubscribeCmds; + private NotificationsCountSubCmd unreadNotificationsCountSubCmd; + + private NotificationsSubCmd unreadNotificationsSubCmd; + + private MarkNotificationsAsReadCmd markNotificationAsReadCmd; + + private MarkAllNotificationsAsReadCmd markAllNotificationsAsReadCmd; + + private NotificationsUnsubCmd notificationsUnsubCmd; + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java index dc9d3a48c2..5081e71d0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java @@ -15,10 +15,12 @@ */ package org.thingsboard.server.service.ws.telemetry.cmd.v1; +import org.thingsboard.server.service.ws.notification.cmd.WsCmd; + /** * @author Andrew Shvayka */ -public interface TelemetryPluginCmd { +public interface TelemetryPluginCmd extends WsCmd { int getCmdId(); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java index c09cbc03d1..282e95f511 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java @@ -17,9 +17,10 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import lombok.Getter; +import org.thingsboard.server.service.ws.notification.cmd.WsCmd; @Data -public class DataCmd { +public class DataCmd implements WsCmd { @Getter private final int cmdId; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java index 81288c76da..3bdc70b503 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.service.ws.telemetry.cmd.v2; -public interface UnsubscribeCmd { +import org.thingsboard.server.service.ws.notification.cmd.WsCmd; + +public interface UnsubscribeCmd extends WsCmd { int getCmdId(); diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index ede6c69fd4..43afcfc845 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; @@ -106,19 +106,19 @@ public class TbTestWebSocketClient extends WebSocketClient { } public void send(EntityDataCmd cmd) throws NotYetConnectedException { - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + WsCmdsWrapper wrapper = new WsCmdsWrapper(); wrapper.setEntityDataCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } public void send(EntityCountCmd cmd) throws NotYetConnectedException { - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + WsCmdsWrapper wrapper = new WsCmdsWrapper(); wrapper.setEntityCountCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } public void send(AlarmCountCmd cmd) throws NotYetConnectedException { - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + WsCmdsWrapper wrapper = new WsCmdsWrapper(); wrapper.setAlarmCountCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } @@ -240,7 +240,7 @@ public class TbTestWebSocketClient extends WebSocketClient { cmd.setEntityId(entityId.getId().toString()); cmd.setScope(scope); cmd.setKeys(String.join(",", keys)); - TelemetryPluginCmdsWrapper cmdsWrapper = new TelemetryPluginCmdsWrapper(); + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); cmdsWrapper.setAttrSubCmds(List.of(cmd)); JsonNode msg = JacksonUtil.valueToTree(cmdsWrapper); ((ObjectNode) msg.get("attrSubCmds").get(0)).remove("type"); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 664ff7bd54..31023fe258 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import java.net.URI; @@ -50,37 +50,37 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { private List notifications; public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { - super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); + super(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + token)); } public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { - NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit)); + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + cmdsWrapper.setUnreadNotificationsSubCmd(new NotificationsSubCmd(1, limit)); sendCmd(cmdsWrapper); this.limit = limit; return this; } public NotificationApiWsClient subscribeForUnreadNotificationsCount() { - NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2)); + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + cmdsWrapper.setUnreadNotificationsCountSubCmd(new NotificationsCountSubCmd(2)); sendCmd(cmdsWrapper); return this; } public void markNotificationAsRead(UUID... notifications) { - NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setMarkAsReadCmd(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications))); + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + cmdsWrapper.setMarkNotificationAsReadCmd(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications))); sendCmd(cmdsWrapper); } public void markAllNotificationsAsRead() { - NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); - cmdsWrapper.setMarkAllAsReadCmd(new MarkAllNotificationsAsReadCmd(newCmdId())); + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + cmdsWrapper.setMarkAllNotificationsAsReadCmd(new MarkAllNotificationsAsReadCmd(newCmdId())); sendCmd(cmdsWrapper); } - public void sendCmd(NotificationCmdsWrapper cmdsWrapper) { + public void sendCmd(WsCmdsWrapper cmdsWrapper) { String cmd = JacksonUtil.toString(cmdsWrapper); send(cmd); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 64382d5d51..3dd011ac78 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; @@ -229,7 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + WsCmdsWrapper wrapper = new WsCmdsWrapper(); wrapper.setEntityDataCmds(Collections.singletonList(cmd)); getWsClient().send(JacksonUtil.toString(wrapper)); From 26cd9b226d7f934cc901ef80114a88bd6f9fa920 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 29 Nov 2023 13:44:58 +0200 Subject: [PATCH 02/14] Single validateSessionMetadata on WS cmds --- .../server/service/ws/DefaultWebSocketService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 47478b3fdb..895699756a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -259,6 +259,8 @@ public class DefaultWebSocketService implements WebSocketService { log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e); } + } else { + return; } } } @@ -850,10 +852,6 @@ public class DefaultWebSocketService implements WebSocketService { return true; } - private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { - return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId); - } - private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, int cmdId, String sessionId) { WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { From 9e9c63fea2845847ad8cc88e63b97bd4337f03df Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 4 Dec 2023 15:02:45 +0200 Subject: [PATCH 03/14] WS api refactoring --- .../service/ws/DefaultWebSocketService.java | 51 +++++++++---------- .../cmd/NotificationCmdsWrapper.java | 20 ++++---- ...mdsWrapper.java => WsCommandsWrapper.java} | 12 ++--- .../controller/TbTestWebSocketClient.java | 10 ++-- .../notification/NotificationApiWsClient.java | 20 ++++---- .../lwm2m/AbstractLwM2MIntegrationTest.java | 4 +- 6 files changed, 57 insertions(+), 60 deletions(-) rename application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/{WsCmdsWrapper.java => WsCommandsWrapper.java} (87%) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 895699756a..85f8bd7207 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -66,7 +66,7 @@ import org.thingsboard.server.service.subscription.TbTimeSeriesSubscription; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.WsCmd; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; @@ -160,22 +160,22 @@ public class DefaultWebSocketService implements WebSocketService { pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); cmdsHandlers = List.of( - newCmdsHandler(WsCmdsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), - newCmdsHandler(WsCmdsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), - newCmdsHandler(WsCmdsWrapper::getHistoryCmds, this::handleWsHistoryCmd), - newCmdsHandler(WsCmdsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), - newCmdsHandler(WsCmdsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), - newCmdsHandler(WsCmdsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), - newCmdsHandler(WsCmdsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), - newCmdsHandler(WsCmdsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCmdsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdHandler(WsCmdsWrapper::getUnreadNotificationsSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), - newCmdHandler(WsCmdsWrapper::getUnreadNotificationsCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), - newCmdHandler(WsCmdsWrapper::getMarkNotificationAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), - newCmdHandler(WsCmdsWrapper::getMarkAllNotificationsAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd), - newCmdHandler(WsCmdsWrapper::getNotificationsUnsubCmd, notificationCmdsHandler::handleUnsubCmd) + newCmdsHandler(WsCommandsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), + newCmdsHandler(WsCommandsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), + newCmdsHandler(WsCommandsWrapper::getHistoryCmds, this::handleWsHistoryCmd), + newCmdsHandler(WsCommandsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), + newCmdsHandler(WsCommandsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), + newCmdsHandler(WsCommandsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), + newCmdsHandler(WsCommandsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), + newCmdsHandler(WsCommandsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCommandsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCommandsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCommandsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), + newCmdsHandler(WsCommandsWrapper::getUnreadNotificationsSubCmds, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + newCmdsHandler(WsCommandsWrapper::getUnreadNotificationsCountSubCmds, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + newCmdsHandler(WsCommandsWrapper::getMarkNotificationAsReadCmds, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdsHandler(WsCommandsWrapper::getMarkAllNotificationsAsReadCmds, notificationCmdsHandler::handleMarkAllAsReadCmd), + newCmdsHandler(WsCommandsWrapper::getNotificationsUnsubCmds, notificationCmdsHandler::handleUnsubCmd) ); } @@ -232,7 +232,7 @@ public class DefaultWebSocketService implements WebSocketService { } private void processCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { - WsCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, WsCmdsWrapper.class); + WsCommandsWrapper cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); processCmds(sessionRef, cmdsWrapper); } @@ -241,7 +241,7 @@ public class DefaultWebSocketService implements WebSocketService { processCmds(sessionRef, cmdsWrapper.toCommonCmdsWrapper()); } - private void processCmds(WebSocketSessionRef sessionRef, WsCmdsWrapper cmdsWrapper) { + private void processCmds(WebSocketSessionRef sessionRef, WsCommandsWrapper cmdsWrapper) { if (cmdsWrapper == null) { return; } @@ -1033,22 +1033,17 @@ public class DefaultWebSocketService implements WebSocketService { } - public static WsCmdHandler newCmdHandler(java.util.function.Function cmdExtractor, - BiConsumer handler) { - return new WsCmdHandler<>(cmdExtractor, handler); - } - - public static WsCmdsHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, + public static WsCmdsHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, BiConsumer handler) { return new WsCmdsHandler<>(cmdsExtractor, handler); } @RequiredArgsConstructor public static class WsCmdsHandler { - private final java.util.function.Function> cmdsExtractor; + private final java.util.function.Function> cmdsExtractor; protected final BiConsumer handler; - public List extract(WsCmdsWrapper cmdsWrapper) { + public List extract(WsCommandsWrapper cmdsWrapper) { return cmdsExtractor.apply(cmdsWrapper); } @@ -1059,7 +1054,7 @@ public class DefaultWebSocketService implements WebSocketService { } public static class WsCmdHandler extends WsCmdsHandler { - public WsCmdHandler(java.util.function.Function cmdExtractor, BiConsumer handler) { + public WsCmdHandler(java.util.function.Function cmdExtractor, BiConsumer handler) { super(cmdsWrapper -> { C cmd = cmdExtractor.apply(cmdsWrapper); return cmd != null ? List.of(cmd) : null; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index de7a6adab7..664cfcef5b 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -17,10 +17,12 @@ package org.thingsboard.server.service.ws.notification.cmd; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; + +import java.util.List; /** - * @deprecated Use {@link org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper}. This class is left for backward compatibility + * @deprecated Use {@link WsCommandsWrapper}. This class is left for backward compatibility * */ @Data @Deprecated @@ -37,13 +39,13 @@ public class NotificationCmdsWrapper { private NotificationsUnsubCmd unsubCmd; @JsonIgnore - public WsCmdsWrapper toCommonCmdsWrapper() { - WsCmdsWrapper wrapper = new WsCmdsWrapper(); - wrapper.setUnreadNotificationsCountSubCmd(unreadCountSubCmd); - wrapper.setUnreadNotificationsSubCmd(unreadSubCmd); - wrapper.setMarkNotificationAsReadCmd(markAsReadCmd); - wrapper.setMarkAllNotificationsAsReadCmd(markAllAsReadCmd); - wrapper.setNotificationsUnsubCmd(unsubCmd); + public WsCommandsWrapper toCommonCmdsWrapper() { + WsCommandsWrapper wrapper = new WsCommandsWrapper(); + wrapper.setUnreadNotificationsCountSubCmds(List.of(unreadCountSubCmd)); + wrapper.setUnreadNotificationsSubCmds(List.of(unreadSubCmd)); + wrapper.setMarkNotificationAsReadCmds(List.of(markAsReadCmd)); + wrapper.setMarkAllNotificationsAsReadCmds(List.of(markAllAsReadCmd)); + wrapper.setNotificationsUnsubCmds(List.of(unsubCmd)); return wrapper; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java similarity index 87% rename from application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java index 40eb672030..34d2acfbb9 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java @@ -39,7 +39,7 @@ import java.util.List; * @author Andrew Shvayka */ @Data -public class WsCmdsWrapper { +public class WsCommandsWrapper { private List attrSubCmds; @@ -63,14 +63,14 @@ public class WsCmdsWrapper { private List alarmCountUnsubscribeCmds; - private NotificationsCountSubCmd unreadNotificationsCountSubCmd; + private List unreadNotificationsCountSubCmds; - private NotificationsSubCmd unreadNotificationsSubCmd; + private List unreadNotificationsSubCmds; - private MarkNotificationsAsReadCmd markNotificationAsReadCmd; + private List markNotificationAsReadCmds; - private MarkAllNotificationsAsReadCmd markAllNotificationsAsReadCmd; + private List markAllNotificationsAsReadCmds; - private NotificationsUnsubCmd notificationsUnsubCmd; + private List notificationsUnsubCmds; } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 43afcfc845..a365a36cf4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; @@ -106,19 +106,19 @@ public class TbTestWebSocketClient extends WebSocketClient { } public void send(EntityDataCmd cmd) throws NotYetConnectedException { - WsCmdsWrapper wrapper = new WsCmdsWrapper(); + WsCommandsWrapper wrapper = new WsCommandsWrapper(); wrapper.setEntityDataCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } public void send(EntityCountCmd cmd) throws NotYetConnectedException { - WsCmdsWrapper wrapper = new WsCmdsWrapper(); + WsCommandsWrapper wrapper = new WsCommandsWrapper(); wrapper.setEntityCountCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } public void send(AlarmCountCmd cmd) throws NotYetConnectedException { - WsCmdsWrapper wrapper = new WsCmdsWrapper(); + WsCommandsWrapper wrapper = new WsCommandsWrapper(); wrapper.setAlarmCountCmds(Collections.singletonList(cmd)); this.send(JacksonUtil.toString(wrapper)); } @@ -240,7 +240,7 @@ public class TbTestWebSocketClient extends WebSocketClient { cmd.setEntityId(entityId.getId().toString()); cmd.setScope(scope); cmd.setKeys(String.join(",", keys)); - WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); cmdsWrapper.setAttrSubCmds(List.of(cmd)); JsonNode msg = JacksonUtil.valueToTree(cmdsWrapper); ((ObjectNode) msg.get("attrSubCmds").get(0)).remove("type"); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 31023fe258..1c4772ebf5 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -28,7 +28,7 @@ import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubC import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import java.net.URI; @@ -54,33 +54,33 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { } public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { - WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setUnreadNotificationsSubCmd(new NotificationsSubCmd(1, limit)); + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setUnreadNotificationsSubCmds(List.of(new NotificationsSubCmd(1, limit))); sendCmd(cmdsWrapper); this.limit = limit; return this; } public NotificationApiWsClient subscribeForUnreadNotificationsCount() { - WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setUnreadNotificationsCountSubCmd(new NotificationsCountSubCmd(2)); + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setUnreadNotificationsCountSubCmds(List.of(new NotificationsCountSubCmd(2))); sendCmd(cmdsWrapper); return this; } public void markNotificationAsRead(UUID... notifications) { - WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setMarkNotificationAsReadCmd(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications))); + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setMarkNotificationAsReadCmds(List.of(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications)))); sendCmd(cmdsWrapper); } public void markAllNotificationsAsRead() { - WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setMarkAllNotificationsAsReadCmd(new MarkAllNotificationsAsReadCmd(newCmdId())); + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setMarkAllNotificationsAsReadCmds(List.of(new MarkAllNotificationsAsReadCmd(newCmdId()))); sendCmd(cmdsWrapper); } - public void sendCmd(WsCmdsWrapper cmdsWrapper) { + public void sendCmd(WsCommandsWrapper cmdsWrapper) { String cmd = JacksonUtil.toString(cmdsWrapper); send(cmd); } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 3dd011ac78..94c18f1b73 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; @@ -229,7 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - WsCmdsWrapper wrapper = new WsCmdsWrapper(); + WsCommandsWrapper wrapper = new WsCommandsWrapper(); wrapper.setEntityDataCmds(Collections.singletonList(cmd)); getWsClient().send(JacksonUtil.toString(wrapper)); From 73c7abcf86dc68fee482f8d7ab709bf8446a3e21 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 4 Dec 2023 17:45:27 +0200 Subject: [PATCH 04/14] Fix cmds conversion --- .../notification/cmd/NotificationCmdsWrapper.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 664cfcef5b..5161ce8072 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -41,12 +41,16 @@ public class NotificationCmdsWrapper { @JsonIgnore public WsCommandsWrapper toCommonCmdsWrapper() { WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setUnreadNotificationsCountSubCmds(List.of(unreadCountSubCmd)); - wrapper.setUnreadNotificationsSubCmds(List.of(unreadSubCmd)); - wrapper.setMarkNotificationAsReadCmds(List.of(markAsReadCmd)); - wrapper.setMarkAllNotificationsAsReadCmds(List.of(markAllAsReadCmd)); - wrapper.setNotificationsUnsubCmds(List.of(unsubCmd)); + wrapper.setUnreadNotificationsCountSubCmds(toList(unreadCountSubCmd)); + wrapper.setUnreadNotificationsSubCmds(toList(unreadSubCmd)); + wrapper.setMarkNotificationAsReadCmds(toList(markAsReadCmd)); + wrapper.setMarkAllNotificationsAsReadCmds(toList(markAllAsReadCmd)); + wrapper.setNotificationsUnsubCmds(toList(unsubCmd)); return wrapper; } + private List toList(C cmd) { + return cmd != null ? List.of(cmd) : null; + } + } From ca29c79a9f9aad139f3e4fa9a7b7c923d67d3818 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 5 Dec 2023 18:10:29 +0200 Subject: [PATCH 05/14] WS cmds structure refactoring; new WS api endpoint --- .../server/config/WebSocketConfiguration.java | 7 +- .../controller/plugin/TbWebSocketHandler.java | 53 ++-- .../service/ws/DefaultWebSocketService.java | 264 ++++++++---------- .../service/ws/WebSocketSessionType.java | 16 +- .../ws/{notification/cmd => }/WsCmd.java | 10 +- .../server/service/ws/WsCmdType.java | 37 +++ .../server/service/ws/WsCommandsWrapper.java | 69 +++++ .../cmd/MarkAllNotificationsAsReadCmd.java | 7 + .../cmd/MarkNotificationsAsReadCmd.java | 7 + .../cmd/NotificationCmdsWrapper.java | 22 +- .../cmd/NotificationsCountSubCmd.java | 7 + .../notification/cmd/NotificationsSubCmd.java | 7 + .../cmd/NotificationsUnsubCmd.java | 7 + ...Wrapper.java => TelemetryCmdsWrapper.java} | 39 +-- .../cmd/v1/AttributesSubscriptionCmd.java | 6 +- .../ws/telemetry/cmd/v1/GetHistoryCmd.java | 5 + .../ws/telemetry/cmd/v1/SubscriptionCmd.java | 2 - .../telemetry/cmd/v1/TelemetryPluginCmd.java | 2 +- .../cmd/v1/TimeseriesSubscriptionCmd.java | 8 +- .../ws/telemetry/cmd/v2/AlarmCountCmd.java | 6 + .../cmd/v2/AlarmCountUnsubscribeCmd.java | 5 + .../ws/telemetry/cmd/v2/AlarmDataCmd.java | 6 + .../cmd/v2/AlarmDataUnsubscribeCmd.java | 5 + .../service/ws/telemetry/cmd/v2/DataCmd.java | 4 +- .../ws/telemetry/cmd/v2/EntityCountCmd.java | 6 + .../cmd/v2/EntityCountUnsubscribeCmd.java | 5 + .../ws/telemetry/cmd/v2/EntityDataCmd.java | 5 + .../cmd/v2/EntityDataUnsubscribeCmd.java | 5 + .../ws/telemetry/cmd/v2/UnsubscribeCmd.java | 2 +- .../controller/AbstractControllerTest.java | 10 +- .../controller/TbTestWebSocketClient.java | 36 +-- .../server/controller/WebsocketApiTest.java | 7 +- .../notification/NotificationApiWsClient.java | 24 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 7 +- 34 files changed, 416 insertions(+), 292 deletions(-) rename application/src/main/java/org/thingsboard/server/service/ws/{notification/cmd => }/WsCmd.java (82%) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java rename application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/{WsCommandsWrapper.java => TelemetryCmdsWrapper.java} (70%) diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java index 9a6ffb0e32..e6097ec5d2 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java @@ -46,8 +46,9 @@ import java.util.Map; @Slf4j public class WebSocketConfiguration implements WebSocketConfigurer { - public static final String WS_PLUGIN_PREFIX = "/api/ws/plugins/"; - private static final String WS_PLUGIN_MAPPING = WS_PLUGIN_PREFIX + "**"; + public static final String WS_API_ENDPOINT = "/api/ws"; + public static final String WS_PLUGINS_ENDPOINT = "/api/ws/plugins/"; + private static final String WS_API_MAPPING = "/api/ws/**"; private final WebSocketHandler wsHandler; @@ -65,7 +66,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { log.error("TbWebSocketHandler expected but [{}] provided", wsHandler); throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided"); } - registry.addHandler(wsHandler, WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*") + registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*") .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() { @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index e1f20be0e4..de073aca9a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.controller.plugin; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -52,7 +53,6 @@ import javax.websocket.SendHandler; import javax.websocket.SendResult; import javax.websocket.Session; import java.io.IOException; -import java.net.URI; import java.security.InvalidParameterException; import java.util.Optional; import java.util.Queue; @@ -199,17 +199,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private WebSocketSessionRef toRef(WebSocketSession session) throws IOException { - URI sessionUri = session.getUri(); - String path = sessionUri.getPath(); - path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length()); - if (path.length() == 0) { - throw new IllegalArgumentException("URL should contain plugin token!"); + private WebSocketSessionRef toRef(WebSocketSession session) { + String path = session.getUri().getPath(); + WebSocketSessionType sessionType; + if (path.equals(WebSocketConfiguration.WS_API_ENDPOINT)) { + sessionType = WebSocketSessionType.GENERAL; + } else { + String type = StringUtils.substringAfter(path, WebSocketConfiguration.WS_PLUGINS_ENDPOINT); + sessionType = WebSocketSessionType.forName(type) + .orElseThrow(() -> new InvalidParameterException("Unknown session type")); } - String[] pathElements = path.split("/"); - String serviceToken = pathElements[0]; - WebSocketSessionType sessionType = WebSocketSessionType.forName(serviceToken) - .orElseThrow(() -> new InvalidParameterException("Can't find plugin with specified token!")); SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); return WebSocketSessionRef.builder() @@ -411,10 +410,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } if (!limitAllowed) { - log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached" - , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); - session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!")); - return false; + log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached" + , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); + session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!")); + return false; } } @@ -428,10 +427,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } if (!limitAllowed) { - log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached" - , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); - session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached")); - return false; + log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached" + , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); + session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached")); + return false; } } if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0 @@ -444,10 +443,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } if (!limitAllowed) { - log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached" - , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); - session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached")); - return false; + log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached" + , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); + session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached")); + return false; } } if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0 @@ -460,10 +459,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } if (!limitAllowed) { - log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached" - , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); - session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached")); - return false; + log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached" + , sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId); + session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached")); + return false; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 85f8bd7207..0d7dc7fbfe 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -21,8 +21,10 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; @@ -65,8 +67,7 @@ import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbTimeSeriesSubscription; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; -import org.thingsboard.server.service.ws.notification.cmd.WsCmd; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; @@ -149,7 +150,7 @@ public class DefaultWebSocketService implements WebSocketService { private ScheduledExecutorService pingExecutor; private String serviceId; - private List> cmdsHandlers; + private List> cmdsHandlers; @PostConstruct public void init() { @@ -160,22 +161,22 @@ public class DefaultWebSocketService implements WebSocketService { pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS); cmdsHandlers = List.of( - newCmdsHandler(WsCommandsWrapper::getAttrSubCmds, this::handleWsAttributesSubscriptionCmd), - newCmdsHandler(WsCommandsWrapper::getTsSubCmds, this::handleWsTimeseriesSubscriptionCmd), - newCmdsHandler(WsCommandsWrapper::getHistoryCmds, this::handleWsHistoryCmd), - newCmdsHandler(WsCommandsWrapper::getEntityDataCmds, this::handleWsEntityDataCmd), - newCmdsHandler(WsCommandsWrapper::getAlarmDataCmds, this::handleWsAlarmDataCmd), - newCmdsHandler(WsCommandsWrapper::getEntityCountCmds, this::handleWsEntityCountCmd), - newCmdsHandler(WsCommandsWrapper::getAlarmCountCmds, this::handleWsAlarmCountCmd), - newCmdsHandler(WsCommandsWrapper::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCommandsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCommandsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCommandsWrapper::getAlarmCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd), - newCmdsHandler(WsCommandsWrapper::getUnreadNotificationsSubCmds, notificationCmdsHandler::handleUnreadNotificationsSubCmd), - newCmdsHandler(WsCommandsWrapper::getUnreadNotificationsCountSubCmds, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), - newCmdsHandler(WsCommandsWrapper::getMarkNotificationAsReadCmds, notificationCmdsHandler::handleMarkAsReadCmd), - newCmdsHandler(WsCommandsWrapper::getMarkAllNotificationsAsReadCmds, notificationCmdsHandler::handleMarkAllAsReadCmd), - newCmdsHandler(WsCommandsWrapper::getNotificationsUnsubCmds, notificationCmdsHandler::handleUnsubCmd) + newCmdHandler(WsCmdType.ATTRIBUTES, this::handleWsAttributesSubscriptionCmd), + newCmdHandler(WsCmdType.TIMESERIES, this::handleWsTimeseriesSubscriptionCmd), + newCmdHandler(WsCmdType.TIMESERIES_HISTORY, this::handleWsHistoryCmd), + newCmdHandler(WsCmdType.ENTITY_DATA, this::handleWsEntityDataCmd), + newCmdHandler(WsCmdType.ALARM_DATA, this::handleWsAlarmDataCmd), + newCmdHandler(WsCmdType.ENTITY_COUNT, this::handleWsEntityCountCmd), + newCmdHandler(WsCmdType.ALARM_COUNT, this::handleWsAlarmCountCmd), + newCmdHandler(WsCmdType.ENTITY_DATA_UNSUBSCRIBE, this::handleWsDataUnsubscribeCmd), + newCmdHandler(WsCmdType.ALARM_DATA_UNSUBSCRIBE, this::handleWsDataUnsubscribeCmd), + newCmdHandler(WsCmdType.ENTITY_COUNT_UNSUBSCRIBE, this::handleWsDataUnsubscribeCmd), + newCmdHandler(WsCmdType.ALARM_COUNT_UNSUBSCRIBE, this::handleWsDataUnsubscribeCmd), + newCmdHandler(WsCmdType.NOTIFICATIONS, notificationCmdsHandler::handleUnreadNotificationsSubCmd), + newCmdHandler(WsCmdType.NOTIFICATIONS_COUNT, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), + newCmdHandler(WsCmdType.MARK_NOTIFICATIONS_AS_READ, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdHandler(WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ, notificationCmdsHandler::handleMarkAllAsReadCmd), + newCmdHandler(WsCmdType.NOTIFICATIONS_UNSUBSCRIBE, notificationCmdsHandler::handleUnsubCmd) ); } @@ -217,93 +218,71 @@ public class DefaultWebSocketService implements WebSocketService { } try { + WsCommandsWrapper cmdsWrapper; switch (sessionRef.getSessionType()) { case GENERAL: - processCmds(sessionRef, msg); + cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); + break; + case TELEMETRY: + cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper(); break; case NOTIFICATIONS: - processNotificationCmds(sessionRef, msg); + cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper(); break; + default: + throw new IllegalArgumentException("Unknown session type"); } - } catch (IOException e) { + processCmds(sessionRef, cmdsWrapper); + } catch (Exception e) { log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(UNKNOWN_SUBSCRIPTION_ID, SubscriptionErrorCode.BAD_REQUEST, FAILED_TO_PARSE_WS_COMMAND)); } } - private void processCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException { - WsCommandsWrapper cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); - processCmds(sessionRef, cmdsWrapper); - } - - private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException { - NotificationCmdsWrapper cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class); - processCmds(sessionRef, cmdsWrapper.toCommonCmdsWrapper()); - } - private void processCmds(WebSocketSessionRef sessionRef, WsCommandsWrapper cmdsWrapper) { - if (cmdsWrapper == null) { + if (cmdsWrapper == null || CollectionUtils.isEmpty(cmdsWrapper.getCmds())) { return; } String sessionId = sessionRef.getSessionId(); - for (WsCmdsHandler cmdHandler : cmdsHandlers) { - List cmds = cmdHandler.extract(cmdsWrapper); - if (cmds == null) { - continue; - } - for (WsCmd cmd : cmds) { - if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { - try { - cmdHandler.handle(sessionRef, cmd); - } catch (Exception e) { - log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId, - sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e); - } - } else { - return; - } + if (!validateSessionMetadata(sessionRef, cmdsWrapper.getCmds().get(0).getCmdId(), sessionId)) { + return; + } + + for (WsCmd cmd : cmdsWrapper.getCmds()) { + log.debug("[{}][{}][{}] Processing cmd: {}", sessionId, cmd.getType(), cmd.getCmdId(), cmd); + try { + getCmdHandler(cmd.getType()).handle(sessionRef, cmd); + } catch (Exception e) { + log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId, + sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e); } } } private void handleWsEntityDataCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { - String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } private void handleWsEntityCountCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { - String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } private void handleWsAlarmDataCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { - String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - if (validateSubscriptionCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { - String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); entityDataSubService.cancelSubscription(sessionRef.getSessionId(), cmd); } private void handleWsAlarmCountCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { - String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - - if (validateSubscriptionCmd(sessionRef, cmd)) { + if (validateCmd(sessionRef, cmd)) { entityDataSubService.handleCmd(sessionRef, cmd); } } @@ -449,8 +428,6 @@ public class DefaultWebSocketService implements WebSocketService { } String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - if (cmd.isUnsubscribe()) { unsubscribe(sessionRef, cmd, sessionId); } else if (validateSubscriptionCmd(sessionRef, cmd)) { @@ -535,18 +512,15 @@ public class DefaultWebSocketService implements WebSocketService { } private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) { - if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Device id is empty!"); - sendWsMsg(sessionRef, update); - return; - } - if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Keys are empty!"); - sendWsMsg(sessionRef, update); - return; - } + if (!validateCmd(sessionRef, cmd, () -> { + if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) { + throw new IllegalArgumentException("Device id is empty!"); + } + if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) { + throw new IllegalArgumentException("Keys are empty!"); + } + })) return; + EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); List keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); List queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))) @@ -623,9 +597,7 @@ public class DefaultWebSocketService implements WebSocketService { @Override public void onFailure(Throwable e) { log.error(FAILED_TO_FETCH_ATTRIBUTES, e); - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, - FAILED_TO_FETCH_ATTRIBUTES); - sendWsMsg(sessionRef, update); + sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_ATTRIBUTES); } }; @@ -643,8 +615,6 @@ public class DefaultWebSocketService implements WebSocketService { } String sessionId = sessionRef.getSessionId(); - log.debug("[{}] Processing: {}", sessionId, cmd); - if (cmd.isUnsubscribe()) { unsubscribe(sessionRef, cmd, sessionId); } else if (validateSubscriptionCmd(sessionRef, cmd)) { @@ -783,9 +753,7 @@ public class DefaultWebSocketService implements WebSocketService { } else { log.info(FAILED_TO_FETCH_DATA, e); } - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, - FAILED_TO_FETCH_DATA); - sendWsMsg(sessionRef, update); + sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_DATA); } }; } @@ -799,82 +767,73 @@ public class DefaultWebSocketService implements WebSocketService { } private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) { - if (cmd.getCmdId() < 0) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Cmd id is negative value!"); - sendWsMsg(sessionRef, update); - return false; - } else if (cmd.getQuery() == null && !cmd.hasAnyCmd()) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Query is empty!"); - sendWsMsg(sessionRef, update); - return false; - } - return true; + return validateCmd(sessionRef, cmd, () -> { + if (cmd.getQuery() == null && !cmd.hasAnyCmd()) { + throw new IllegalArgumentException("Query is empty!"); + } + }); } private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) { - if (cmd.getCmdId() < 0) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Cmd id is negative value!"); - sendWsMsg(sessionRef, update); - return false; - } else if (cmd.getQuery() == null) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!"); - sendWsMsg(sessionRef, update); - return false; - } - return true; + return validateCmd(sessionRef, cmd, () -> { + if (cmd.getQuery() == null) { + throw new IllegalArgumentException("Query is empty!"); + } + }); } private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) { - if (cmd.getCmdId() < 0) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Cmd id is negative value!"); - sendWsMsg(sessionRef, update); - return false; - } else if (cmd.getQuery() == null) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Query is empty!"); - sendWsMsg(sessionRef, update); - return false; - } - return true; + return validateCmd(sessionRef, cmd, () -> { + if (cmd.getQuery() == null) { + throw new IllegalArgumentException("Query is empty!"); + } + }); } private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) { - if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Device id is empty!"); - sendWsMsg(sessionRef, update); - return false; - } - return true; + return validateCmd(sessionRef, cmd, () -> { + if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { + throw new IllegalArgumentException("Device id is empty!"); + } + }); } private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, int cmdId, String sessionId) { WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); if (sessionMD == null) { log.warn("[{}] Session meta data not found. ", sessionId); - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmdId, SubscriptionErrorCode.INTERNAL_ERROR, - SESSION_META_DATA_NOT_FOUND); - sendWsMsg(sessionRef, update); + sendError(sessionRef, cmdId, SubscriptionErrorCode.INTERNAL_ERROR, SESSION_META_DATA_NOT_FOUND); return false; } else { return true; } } - private boolean validateSubscriptionCmd(WebSocketSessionRef sessionRef, AlarmCountCmd cmd) { + private boolean validateCmd(WebSocketSessionRef sessionRef, WsCmd cmd) { + return validateCmd(sessionRef, cmd, null); + } + + private boolean validateCmd(WebSocketSessionRef sessionRef, C cmd, Runnable validator) { if (cmd.getCmdId() < 0) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, - "Cmd id is negative value!"); - sendWsMsg(sessionRef, update); + sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Cmd id is negative value!"); + return false; + } + try { + if (validator != null) { + validator.run(); + } + } catch (Exception e) { + sendError(sessionRef, cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, e.getMessage()); return false; } return true; } + private void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg) { + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(subId, errorCode, errorMsg); + sendWsMsg(sessionRef, update); + } + private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) { sendWsMsg(sessionRef, update.getCmdId(), update); } @@ -1032,34 +991,29 @@ public class DefaultWebSocketService implements WebSocketService { .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } + public WsCmdHandler getCmdHandler(WsCmdType cmdType) { + for (WsCmdHandler cmdHandler : cmdsHandlers) { + if (cmdHandler.getCmdType() == cmdType) { + return cmdHandler; + } + } + throw new IllegalArgumentException("Unknown command type " + cmdType); + } - public static WsCmdsHandler newCmdsHandler(java.util.function.Function> cmdsExtractor, - BiConsumer handler) { - return new WsCmdsHandler<>(cmdsExtractor, handler); + public static WsCmdHandler newCmdHandler(WsCmdType cmdType, BiConsumer handler) { + return new WsCmdHandler<>(cmdType, handler); } @RequiredArgsConstructor - public static class WsCmdsHandler { - private final java.util.function.Function> cmdsExtractor; + @Getter + @SuppressWarnings("unchecked") + public static class WsCmdHandler { + private final WsCmdType cmdType; protected final BiConsumer handler; - public List extract(WsCommandsWrapper cmdsWrapper) { - return cmdsExtractor.apply(cmdsWrapper); - } - - @SuppressWarnings("unchecked") - public void handle(WebSocketSessionRef sessionRef, Object cmd) { + public void handle(WebSocketSessionRef sessionRef, WsCmd cmd) { handler.accept(sessionRef, (C) cmd); } } - public static class WsCmdHandler extends WsCmdsHandler { - public WsCmdHandler(java.util.function.Function cmdExtractor, BiConsumer handler) { - super(cmdsWrapper -> { - C cmd = cmdExtractor.apply(cmdsWrapper); - return cmd != null ? List.of(cmd) : null; - }, handler); - } - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java index 5547ef3359..af2206686f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java @@ -15,23 +15,25 @@ */ package org.thingsboard.server.service.ws; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.Optional; -@RequiredArgsConstructor -@Getter +@NoArgsConstructor +@AllArgsConstructor public enum WebSocketSessionType { - GENERAL("telemetry"), + GENERAL(), + TELEMETRY("telemetry"), // deprecated NOTIFICATIONS("notifications"); // deprecated - private final String name; + private String name; public static Optional forName(String name) { return Arrays.stream(values()) - .filter(sessionType -> sessionType.getName().equals(name)) + .filter(sessionType -> StringUtils.equals(sessionType.name, name)) .findFirst(); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java rename to application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java index 97bfeab70c..0f4678cf76 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmd.java @@ -13,8 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ws.notification.cmd; +package org.thingsboard.server.service.ws; + +import com.fasterxml.jackson.annotation.JsonIgnore; + public interface WsCmd { + int getCmdId(); + + @JsonIgnore + WsCmdType getType(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java new file mode 100644 index 0000000000..ef1d8520a2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.service.ws; + +public enum WsCmdType { + ATTRIBUTES, + TIMESERIES, + TIMESERIES_HISTORY, + ENTITY_DATA, + ENTITY_COUNT, + ALARM_DATA, + ALARM_COUNT, + + NOTIFICATIONS, + NOTIFICATIONS_COUNT, + MARK_NOTIFICATIONS_AS_READ, + MARK_ALL_NOTIFICATIONS_AS_READ, + + ALARM_DATA_UNSUBSCRIBE, + ALARM_COUNT_UNSUBSCRIBE, + ENTITY_DATA_UNSUBSCRIBE, + ENTITY_COUNT_UNSUBSCRIBE, + NOTIFICATIONS_UNSUBSCRIBE +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java new file mode 100644 index 0000000000..cbdbdf8c05 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.service.ws; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; +import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WsCommandsWrapper { + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") + @JsonSubTypes({ + @Type(name = "ATTRIBUTES", value = AttributesSubscriptionCmd.class), + @Type(name = "TIMESERIES", value = TimeseriesSubscriptionCmd.class), + @Type(name = "TIMESERIES_HISTORY", value = GetHistoryCmd.class), + @Type(name = "ENTITY_DATA", value = EntityDataCmd.class), + @Type(name = "ENTITY_COUNT", value = EntityCountCmd.class), + @Type(name = "ALARM_DATA", value = AlarmDataCmd.class), + @Type(name = "ALARM_COUNT", value = AlarmCountCmd.class), + @Type(name = "NOTIFICATIONS", value = NotificationsSubCmd.class), + @Type(name = "NOTIFICATIONS_COUNT", value = NotificationsCountSubCmd.class), + @Type(name = "MARK_NOTIFICATIONS_AS_READ", value = MarkNotificationsAsReadCmd.class), + @Type(name = "MARK_ALL_NOTIFICATIONS_AS_READ", value = MarkAllNotificationsAsReadCmd.class), + @Type(name = "ALARM_DATA_UNSUBSCRIBE", value = AlarmDataUnsubscribeCmd.class), + @Type(name = "ALARM_COUNT_UNSUBSCRIBE", value = AlarmCountUnsubscribeCmd.class), + @Type(name = "ENTITY_DATA_UNSUBSCRIBE", value = EntityDataUnsubscribeCmd.class), + @Type(name = "ENTITY_COUNT_UNSUBSCRIBE", value = EntityCountUnsubscribeCmd.class), + @Type(name = "NOTIFICATIONS_UNSUBSCRIBE", value = NotificationsUnsubCmd.class), + }) + private List cmds; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java index f096106135..d11992ba4f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java @@ -18,10 +18,17 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCmdType; @Data @NoArgsConstructor @AllArgsConstructor public class MarkAllNotificationsAsReadCmd implements WsCmd { private int cmdId; + + @Override + public WsCmdType getType() { + return WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java index 55de75387f..2452163fd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java @@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCmdType; import java.util.List; import java.util.UUID; @@ -28,4 +30,9 @@ import java.util.UUID; public class MarkNotificationsAsReadCmd implements WsCmd { private int cmdId; private List notifications; + + @Override + public WsCmdType getType() { + return WsCmdType.MARK_NOTIFICATIONS_AS_READ; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 5161ce8072..3e0eca1fab 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -17,9 +17,11 @@ package org.thingsboard.server.service.ws.notification.cmd; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; +import org.thingsboard.server.service.ws.WsCommandsWrapper; -import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @deprecated Use {@link WsCommandsWrapper}. This class is left for backward compatibility @@ -40,17 +42,11 @@ public class NotificationCmdsWrapper { @JsonIgnore public WsCommandsWrapper toCommonCmdsWrapper() { - WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setUnreadNotificationsCountSubCmds(toList(unreadCountSubCmd)); - wrapper.setUnreadNotificationsSubCmds(toList(unreadSubCmd)); - wrapper.setMarkNotificationAsReadCmds(toList(markAsReadCmd)); - wrapper.setMarkAllNotificationsAsReadCmds(toList(markAllAsReadCmd)); - wrapper.setNotificationsUnsubCmds(toList(unsubCmd)); - return wrapper; - } - - private List toList(C cmd) { - return cmd != null ? List.of(cmd) : null; + return new WsCommandsWrapper(Stream.of( + unreadCountSubCmd, unreadSubCmd, markAsReadCmd, markAllAsReadCmd, unsubCmd + ) + .filter(Objects::nonNull) + .collect(Collectors.toList())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java index 0d6757a6e7..7fa0ac9a6a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java @@ -18,10 +18,17 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCmdType; @Data @NoArgsConstructor @AllArgsConstructor public class NotificationsCountSubCmd implements WsCmd { private int cmdId; + + @Override + public WsCmdType getType() { + return WsCmdType.NOTIFICATIONS_COUNT; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java index e022d2c49f..44d6c47ff6 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCmdType; @Data @NoArgsConstructor @@ -25,4 +27,9 @@ import lombok.NoArgsConstructor; public class NotificationsSubCmd implements WsCmd { private int cmdId; private int limit; + + @Override + public WsCmdType getType() { + return WsCmdType.NOTIFICATIONS; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java index c9f0897eeb..a63351d9b9 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java @@ -18,6 +18,8 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCmdType; import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @Data @@ -25,4 +27,9 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd; @AllArgsConstructor public class NotificationsUnsubCmd implements UnsubscribeCmd, WsCmd { private int cmdId; + + @Override + public WsCmdType getType() { + return WsCmdType.NOTIFICATIONS_UNSUBSCRIBE; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java similarity index 70% rename from application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java rename to application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java index 34d2acfbb9..e2029cbe36 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/WsCommandsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java @@ -15,12 +15,9 @@ */ package org.thingsboard.server.service.ws.telemetry.cmd; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; -import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; -import org.thingsboard.server.service.ws.notification.cmd.NotificationsUnsubCmd; +import org.thingsboard.server.service.ws.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd; @@ -33,13 +30,18 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribe import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * @author Andrew Shvayka - */ + * @deprecated Use {@link WsCommandsWrapper}. This class is left for backward compatibility + * */ @Data -public class WsCommandsWrapper { +@Deprecated +public class TelemetryCmdsWrapper { private List attrSubCmds; @@ -63,14 +65,17 @@ public class WsCommandsWrapper { private List alarmCountUnsubscribeCmds; - private List unreadNotificationsCountSubCmds; - - private List unreadNotificationsSubCmds; - - private List markNotificationAsReadCmds; - - private List markAllNotificationsAsReadCmds; - - private List notificationsUnsubCmds; + @JsonIgnore + public WsCommandsWrapper toCommonCmdsWrapper() { + return new WsCommandsWrapper(Stream.of( + attrSubCmds, tsSubCmds, historyCmds, entityDataCmds, + entityDataUnsubscribeCmds, alarmDataCmds, alarmDataUnsubscribeCmds, + entityCountCmds, entityCountUnsubscribeCmds, + alarmCountCmds, alarmCountUnsubscribeCmds + ) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList())); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java index 1ccec261b0..d1153b8bdd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java @@ -16,7 +16,7 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.WsCmdType; /** * @author Andrew Shvayka @@ -25,8 +25,8 @@ import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; public class AttributesSubscriptionCmd extends SubscriptionCmd { @Override - public TelemetryFeature getType() { - return TelemetryFeature.ATTRIBUTES; + public WsCmdType getType() { + return WsCmdType.ATTRIBUTES; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java index 6791066641..0da067f6da 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.service.ws.WsCmdType; /** * @author Andrew Shvayka @@ -37,4 +38,8 @@ public class GetHistoryCmd implements TelemetryPluginCmd { private int limit; private String agg; + @Override + public WsCmdType getType() { + return WsCmdType.TIMESERIES_HISTORY; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java index 6a5f9d820d..3b742c3a2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java @@ -32,8 +32,6 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd { private String scope; private boolean unsubscribe; - public abstract TelemetryFeature getType(); - @Override public String toString() { return "SubscriptionCmd [entityType=" + entityType + ", entityId=" + entityId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]"; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java index 5081e71d0a..b57b907184 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TelemetryPluginCmd.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.service.ws.telemetry.cmd.v1; -import org.thingsboard.server.service.ws.notification.cmd.WsCmd; +import org.thingsboard.server.service.ws.WsCmd; /** * @author Andrew Shvayka diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java index f0a7b9d3af..aea228eebc 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/TimeseriesSubscriptionCmd.java @@ -17,8 +17,9 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v1; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; +import org.thingsboard.server.service.ws.WsCmdType; /** * @author Andrew Shvayka @@ -26,6 +27,7 @@ import org.thingsboard.server.service.ws.telemetry.TelemetryFeature; @NoArgsConstructor @AllArgsConstructor @Data +@EqualsAndHashCode(callSuper = true) public class TimeseriesSubscriptionCmd extends SubscriptionCmd { private long startTs; @@ -35,7 +37,7 @@ public class TimeseriesSubscriptionCmd extends SubscriptionCmd { private String agg; @Override - public TelemetryFeature getType() { - return TelemetryFeature.TIMESERIES; + public WsCmdType getType() { + return WsCmdType.TIMESERIES; } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java index 80fd998eb1..b1fa9cf785 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountCmd.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.AlarmCountQuery; +import org.thingsboard.server.service.ws.WsCmdType; public class AlarmCountCmd extends DataCmd { @@ -31,4 +32,9 @@ public class AlarmCountCmd extends DataCmd { super(cmdId); this.query = query; } + + @Override + public WsCmdType getType() { + return WsCmdType.ALARM_COUNT; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java index 79c860f3f6..02cf5d5572 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmCountUnsubscribeCmd.java @@ -16,10 +16,15 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; +import org.thingsboard.server.service.ws.WsCmdType; @Data public class AlarmCountUnsubscribeCmd implements UnsubscribeCmd { private final int cmdId; + @Override + public WsCmdType getType() { + return WsCmdType.ALARM_COUNT_UNSUBSCRIBE; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java index 0f83d00d6d..0efa6ae608 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataCmd.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.service.ws.WsCmdType; public class AlarmDataCmd extends DataCmd { @@ -30,4 +31,9 @@ public class AlarmDataCmd extends DataCmd { super(cmdId); this.query = query; } + + @Override + public WsCmdType getType() { + return WsCmdType.ALARM_DATA; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java index 97db558cf9..7dfb1159c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AlarmDataUnsubscribeCmd.java @@ -16,10 +16,15 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; +import org.thingsboard.server.service.ws.WsCmdType; @Data public class AlarmDataUnsubscribeCmd implements UnsubscribeCmd { private final int cmdId; + @Override + public WsCmdType getType() { + return WsCmdType.ALARM_DATA_UNSUBSCRIBE; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java index 282e95f511..74133230cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/DataCmd.java @@ -17,10 +17,10 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; import lombok.Getter; -import org.thingsboard.server.service.ws.notification.cmd.WsCmd; +import org.thingsboard.server.service.ws.WsCmd; @Data -public class DataCmd implements WsCmd { +public abstract class DataCmd implements WsCmd { @Getter private final int cmdId; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java index c39f81f1b7..3431afaf1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountCmd.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.EntityCountQuery; +import org.thingsboard.server.service.ws.WsCmdType; public class EntityCountCmd extends DataCmd { @@ -31,4 +32,9 @@ public class EntityCountCmd extends DataCmd { super(cmdId); this.query = query; } + + @Override + public WsCmdType getType() { + return WsCmdType.ENTITY_COUNT; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java index b82fecd8fd..4e0a50c7e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java @@ -16,10 +16,15 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; +import org.thingsboard.server.service.ws.WsCmdType; @Data public class EntityCountUnsubscribeCmd implements UnsubscribeCmd { private final int cmdId; + @Override + public WsCmdType getType() { + return WsCmdType.ENTITY_COUNT_UNSUBSCRIBE; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java index 5fe9179651..ace96f6673 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataCmd.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.service.ws.WsCmdType; public class EntityDataCmd extends DataCmd { @@ -62,4 +63,8 @@ public class EntityDataCmd extends DataCmd { return historyCmd != null || latestCmd != null || tsCmd != null || aggHistoryCmd != null || aggTsCmd != null; } + @Override + public WsCmdType getType() { + return WsCmdType.ENTITY_DATA; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java index 80577edda2..56727faf54 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/EntityDataUnsubscribeCmd.java @@ -16,10 +16,15 @@ package org.thingsboard.server.service.ws.telemetry.cmd.v2; import lombok.Data; +import org.thingsboard.server.service.ws.WsCmdType; @Data public class EntityDataUnsubscribeCmd implements UnsubscribeCmd { private final int cmdId; + @Override + public WsCmdType getType() { + return WsCmdType.ENTITY_DATA_UNSUBSCRIBE; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java index 3bdc70b503..4efdb279be 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/UnsubscribeCmd.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.service.ws.telemetry.cmd.v2; -import org.thingsboard.server.service.ws.notification.cmd.WsCmd; +import org.thingsboard.server.service.ws.WsCmd; public interface UnsubscribeCmd extends WsCmd { diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index 5809ab5e42..fe09a187e1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -52,8 +52,8 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { @LocalServerPort protected int wsPort; - private volatile TbTestWebSocketClient wsClient; // lazy - private volatile TbTestWebSocketClient anotherWsClient; // lazy + protected volatile TbTestWebSocketClient wsClient; // lazy + protected volatile TbTestWebSocketClient anotherWsClient; // lazy public TbTestWebSocketClient getWsClient() { if (wsClient == null) { @@ -101,7 +101,11 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { } protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { - TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token)); + return buildAndConnectWebSocketClient("/api/ws"); + } + + protected TbTestWebSocketClient buildAndConnectWebSocketClient(String path) throws URISyntaxException, InterruptedException { + TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + path + "?token=" + token)); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); return wsClient; } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index a365a36cf4..0194911e5b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -16,7 +16,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; @@ -28,11 +27,10 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; +import org.thingsboard.server.service.ws.WsCmd; +import org.thingsboard.server.service.ws.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; @@ -105,24 +103,6 @@ public class TbTestWebSocketClient extends WebSocketClient { super.send(text); } - public void send(EntityDataCmd cmd) throws NotYetConnectedException { - WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - this.send(JacksonUtil.toString(wrapper)); - } - - public void send(EntityCountCmd cmd) throws NotYetConnectedException { - WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setEntityCountCmds(Collections.singletonList(cmd)); - this.send(JacksonUtil.toString(wrapper)); - } - - public void send(AlarmCountCmd cmd) throws NotYetConnectedException { - WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setAlarmCountCmds(Collections.singletonList(cmd)); - this.send(JacksonUtil.toString(wrapper)); - } - public String waitForUpdate() { return waitForUpdate(false); } @@ -240,11 +220,7 @@ public class TbTestWebSocketClient extends WebSocketClient { cmd.setEntityId(entityId.getId().toString()); cmd.setScope(scope); cmd.setKeys(String.join(",", keys)); - WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); - cmdsWrapper.setAttrSubCmds(List.of(cmd)); - JsonNode msg = JacksonUtil.valueToTree(cmdsWrapper); - ((ObjectNode) msg.get("attrSubCmds").get(0)).remove("type"); - send(msg.toString()); + send(cmd); return JacksonUtil.toJsonNode(waitForReply()); } @@ -288,4 +264,10 @@ public class TbTestWebSocketClient extends WebSocketClient { return sendEntityDataQuery(edq); } + public void send(WsCmd... cmds) { + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setCmds(List.of(cmds)); + send(JacksonUtil.toString(cmdsWrapper)); + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java index 73ee8f9252..070fda9474 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java @@ -648,7 +648,7 @@ public class WebsocketApiTest extends AbstractControllerTest { } @Test - public void testEntityCountCmd_filterTypeSingularCompatibilityTest() { + public void testEntityCountCmd_filterTypeSingularCompatibilityTest() throws Exception { ObjectNode oldFormatDeviceTypeFilterSingular = JacksonUtil.newObjectNode(); oldFormatDeviceTypeFilterSingular.put("type", "deviceType"); oldFormatDeviceTypeFilterSingular.put("deviceType", "default"); @@ -667,9 +667,10 @@ public class WebsocketApiTest extends AbstractControllerTest { ObjectNode wrapperNode = JacksonUtil.newObjectNode(); wrapperNode.set("entityCountCmds", entityCountCmds); - getWsClient().send(JacksonUtil.toString(wrapperNode)); + wsClient = buildAndConnectWebSocketClient("/api/ws/plugins/telemetry"); + wsClient.send(JacksonUtil.toString(wrapperNode)); - EntityCountUpdate update = getWsClient().parseCountReply(getWsClient().waitForReply()); + EntityCountUpdate update = wsClient.parseCountReply(wsClient.waitForReply()); Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCount()); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 1c4772ebf5..2cff2939c2 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -28,7 +28,6 @@ import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubC import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import java.net.URI; @@ -50,39 +49,26 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { private List notifications; public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { - super(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + token)); + super(new URI(wsUrl + "/api/ws?token=" + token)); } public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { - WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); - cmdsWrapper.setUnreadNotificationsSubCmds(List.of(new NotificationsSubCmd(1, limit))); - sendCmd(cmdsWrapper); + send(new NotificationsSubCmd(1, limit)); this.limit = limit; return this; } public NotificationApiWsClient subscribeForUnreadNotificationsCount() { - WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); - cmdsWrapper.setUnreadNotificationsCountSubCmds(List.of(new NotificationsCountSubCmd(2))); - sendCmd(cmdsWrapper); + send(new NotificationsCountSubCmd(2)); return this; } public void markNotificationAsRead(UUID... notifications) { - WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); - cmdsWrapper.setMarkNotificationAsReadCmds(List.of(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications)))); - sendCmd(cmdsWrapper); + send(new MarkNotificationsAsReadCmd(newCmdId(), Arrays.asList(notifications))); } public void markAllNotificationsAsRead() { - WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); - cmdsWrapper.setMarkAllNotificationsAsReadCmds(List.of(new MarkAllNotificationsAsReadCmd(newCmdId()))); - sendCmd(cmdsWrapper); - } - - public void sendCmd(WsCommandsWrapper cmdsWrapper) { - String cmd = JacksonUtil.toString(cmdsWrapper); - send(cmd); + send(new MarkAllNotificationsAsReadCmd(newCmdId())); } @Override diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 94c18f1b73..7f31ebe74f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.service.ws.telemetry.cmd.WsCommandsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; @@ -229,10 +229,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - WsCommandsWrapper wrapper = new WsCommandsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - getWsClient().send(JacksonUtil.toString(wrapper)); + getWsClient().send(cmd); getWsClient().waitForReply(); getWsClient().registerWaitForUpdate(); From 31f95fbe6205c2c1e0926c2f8645098fab6856c5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 7 Dec 2023 15:11:41 +0200 Subject: [PATCH 06/14] UI: Change ws subscription to single session --- .../core/ws/notification-websocket.service.ts | 108 +++-------------- .../core/ws/telemetry-websocket.service.ts | 75 ++++++++---- ui-ngx/src/app/core/ws/websocket.service.ts | 32 ++--- .../notification-bell.component.ts | 3 +- .../models/telemetry/telemetry.models.ts | 74 +++++++++++- .../websocket/notification-ws.models.ts | 109 ++++-------------- 6 files changed, 181 insertions(+), 220 deletions(-) diff --git a/ui-ngx/src/app/core/ws/notification-websocket.service.ts b/ui-ngx/src/app/core/ws/notification-websocket.service.ts index 8f219c029d..cb33c23635 100644 --- a/ui-ngx/src/app/core/ws/notification-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/notification-websocket.service.ts @@ -15,117 +15,45 @@ /// import { Inject, Injectable, NgZone } from '@angular/core'; +import { + TelemetryPluginCmdsWrapper, + TelemetrySubscriber, + WebsocketDataMsg +} from '@shared/models/telemetry/telemetry.models'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AuthService } from '@core/auth/auth.service'; import { WINDOW } from '@core/services/window.service'; -import { - isNotificationCountUpdateMsg, - isNotificationsUpdateMsg, - MarkAllAsReadCmd, - MarkAsReadCmd, - NotificationCountUpdate, - NotificationPluginCmdWrapper, - NotificationSubscriber, - NotificationsUpdate, - UnreadCountSubCmd, - UnreadSubCmd, - UnsubscribeCmd, - WebsocketNotificationMsg -} from '@shared/models/websocket/notification-ws.models'; import { WebsocketService } from '@core/ws/websocket.service'; - // @dynamic @Injectable({ providedIn: 'root' }) -export class NotificationWebsocketService extends WebsocketService { +export class NotificationWebsocketService extends WebsocketService { - cmdWrapper: NotificationPluginCmdWrapper; - - constructor(protected store: Store, + constructor(private telemetryWebsocketService: TelemetryWebsocketService, + protected store: Store, protected authService: AuthService, protected ngZone: NgZone, @Inject(WINDOW) protected window: Window) { - super(store, authService, ngZone, 'api/ws/plugins/notifications', new NotificationPluginCmdWrapper(), window); - this.errorName = 'WebSocket Notification Error'; + super(store, authService, ngZone, 'api/ws/plugins/telemetry', new TelemetryPluginCmdsWrapper(), window); } - public subscribe(subscriber: NotificationSubscriber) { - this.isActive = true; - subscriber.subscriptionCommands.forEach( - (subscriptionCommand) => { - const cmdId = this.nextCmdId(); - this.subscribersMap.set(cmdId, subscriber); - subscriptionCommand.cmdId = cmdId; - if (subscriptionCommand instanceof UnreadCountSubCmd) { - this.cmdWrapper.unreadCountSubCmd = subscriptionCommand; - } else if (subscriptionCommand instanceof UnreadSubCmd) { - this.cmdWrapper.unreadSubCmd = subscriptionCommand; - } else if (subscriptionCommand instanceof MarkAsReadCmd) { - this.cmdWrapper.markAsReadCmd = subscriptionCommand; - this.subscribersMap.delete(cmdId); - } else if (subscriptionCommand instanceof MarkAllAsReadCmd) { - this.cmdWrapper.markAllAsReadCmd = subscriptionCommand; - this.subscribersMap.delete(cmdId); - } - } - ); - if (this.cmdWrapper.unreadCountSubCmd || this.cmdWrapper.unreadSubCmd) { - this.subscribersCount++; - } - this.publishCommands(); + public subscribe(subscriber: TelemetrySubscriber) { + this.telemetryWebsocketService.subscribe(subscriber); } - public update(subscriber: NotificationSubscriber) { - if (!this.isReconnect) { - subscriber.subscriptionCommands.forEach( - (subscriptionCommand) => { - if (subscriptionCommand.cmdId && subscriptionCommand instanceof UnreadSubCmd) { - this.cmdWrapper.unreadSubCmd = subscriptionCommand; - } - } - ); - this.publishCommands(); - } + public update(subscriber: TelemetrySubscriber) { + this.telemetryWebsocketService.update(subscriber); } - public unsubscribe(subscriber: NotificationSubscriber) { - if (this.isActive) { - subscriber.subscriptionCommands.forEach( - (subscriptionCommand) => { - if (subscriptionCommand instanceof UnreadCountSubCmd - || subscriptionCommand instanceof UnreadSubCmd) { - const unreadCountUnsubscribeCmd = new UnsubscribeCmd(); - unreadCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.unsubCmd = unreadCountUnsubscribeCmd; - } - const cmdId = subscriptionCommand.cmdId; - if (cmdId) { - this.subscribersMap.delete(cmdId); - } - } - ); - this.reconnectSubscribers.delete(subscriber); - this.subscribersCount--; - this.publishCommands(); - } + public unsubscribe(subscriber: TelemetrySubscriber) { + this.telemetryWebsocketService.unsubscribe(subscriber); } - processOnMessage(message: WebsocketNotificationMsg) { - let subscriber: NotificationSubscriber; - if (isNotificationCountUpdateMsg(message)) { - subscriber = this.subscribersMap.get(message.cmdId); - if (subscriber) { - subscriber.onNotificationCountUpdate(new NotificationCountUpdate(message)); - } - } else if (isNotificationsUpdateMsg(message)) { - subscriber = this.subscribersMap.get(message.cmdId); - if (subscriber) { - subscriber.onNotificationsUpdate(new NotificationsUpdate(message)); - } - } + processOnMessage(message: WebsocketDataMsg) { + this.telemetryWebsocketService.processOnMessage(message); } - } diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 56c35328fb..45d8510687 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -16,7 +16,8 @@ import { Inject, Injectable, NgZone } from '@angular/core'; import { - AlarmCountCmd, AlarmCountUnsubscribeCmd, + AlarmCountCmd, + AlarmCountUnsubscribeCmd, AlarmCountUpdate, AlarmDataCmd, AlarmDataUnsubscribeCmd, @@ -28,10 +29,13 @@ import { EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate, - GetHistoryCmd, isAlarmCountUpdateMsg, + GetHistoryCmd, + isAlarmCountUpdateMsg, isAlarmDataUpdateMsg, isEntityCountUpdateMsg, isEntityDataUpdateMsg, + NotificationCountUpdate, + NotificationsUpdate, SubscriptionCmd, SubscriptionUpdate, TelemetryFeature, @@ -45,6 +49,16 @@ import { AppState } from '@core/core.state'; import { AuthService } from '@core/auth/auth.service'; import { WINDOW } from '@core/services/window.service'; import { WebsocketService } from '@core/ws/websocket.service'; +import { + isNotificationCountUpdateMsg, + isNotificationsUpdateMsg, + MarkAllAsReadCmd, + MarkAsReadCmd, + NotificationSubscriber, + UnreadCountSubCmd, + UnreadSubCmd, + UnsubscribeCmd +} from '@shared/models/websocket/notification-ws.models'; // @dynamic @Injectable({ @@ -84,6 +98,16 @@ export class TelemetryWebsocketService extends WebsocketService { if (subscriptionCommand.cmdId && subscriptionCommand instanceof EntityDataCmd) { this.cmdWrapper.entityDataCmds.push(subscriptionCommand); + } else if (subscriptionCommand.cmdId && subscriptionCommand instanceof UnreadSubCmd) { + this.cmdWrapper.unreadNotificationsSubCmds.push(subscriptionCommand); } } ); @@ -131,6 +157,10 @@ export class TelemetryWebsocketService extends WebsocketService implements WsServ lastCmdId = 0; subscribersCount = 0; - subscribersMap = new Map(); + subscribersMap = new Map(); - reconnectSubscribers = new Set(); + reconnectSubscribers = new Set(); - notificationUri: string; + wsUri: string; dataStream: WebSocketSubject; @@ -69,23 +69,23 @@ export abstract class WebsocketService implements WsServ if (!port) { port = '443'; } - this.notificationUri = 'wss:'; + this.wsUri = 'wss:'; } else { if (!port) { port = '80'; } - this.notificationUri = 'ws:'; + this.wsUri = 'ws:'; } - this.notificationUri += `//${this.window.location.hostname}:${port}/${apiEndpoint}`; + this.wsUri += `//${this.window.location.hostname}:${port}/${apiEndpoint}`; } - abstract subscribe(subscriber: T); + abstract subscribe(subscriber: WsSubscriber); abstract update(subscriber: T); abstract unsubscribe(subscriber: T); - abstract processOnMessage(message: any); + abstract processOnMessage(message: WebsocketDataMsg); protected nextCmdId(): number { this.lastCmdId++; @@ -158,8 +158,8 @@ export abstract class WebsocketService implements WsServ } private openSocket(token: string) { - const uri = `${this.notificationUri}?token=${token}`; - this.dataStream = webSocket( + const uri = `${this.wsUri}?token=${token}`; + this.dataStream = webSocket( { url: uri, openObserver: { @@ -176,9 +176,9 @@ export abstract class WebsocketService implements WsServ ); this.dataStream.subscribe({ - next: (message) => { + next: (message: CmdUpdateMsg) => { this.ngZone.runOutsideAngular(() => { - this.onMessage(message as WebsocketNotificationMsg); + this.onMessage(message); }); }, error: (error) => { @@ -208,11 +208,11 @@ export abstract class WebsocketService implements WsServ } } - private onMessage(message: WebsocketNotificationMsg) { + private onMessage(message: CmdUpdateMsg) { if (message.errorCode) { this.showWsError(message.errorCode, message.errorMsg); } else { - this.processOnMessage(message); + this.processOnMessage(message as WebsocketDataMsg); } this.checkToClose(); } diff --git a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts index 8ee5771fe7..7c68b2df65 100644 --- a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts +++ b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts @@ -77,11 +77,11 @@ export class NotificationBellComponent implements OnDestroy { if ($event) { $event.stopPropagation(); } - this.unsubscribeSubscription(); const trigger = createVersionButton._elementRef.nativeElement; if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { + this.unsubscribeSubscription(); const showNotificationPopover = this.popoverService.displayPopover(trigger, this.renderer, this.viewContainerRef, ShowNotificationPopoverComponent, 'bottom', true, null, { @@ -108,5 +108,6 @@ export class NotificationBellComponent implements OnDestroy { private unsubscribeSubscription() { this.notificationCountSubscriber.unsubscribe(); this.notificationSubscriber.unsubscribe(); + this.notificationSubscriber = null; } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 66f39d080a..57e94baef2 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -24,9 +24,11 @@ import { NgZone } from '@angular/core'; import { AlarmCountQuery, AlarmData, - AlarmDataQuery, EntityCountQuery, + AlarmDataQuery, + EntityCountQuery, EntityData, - EntityDataQuery, EntityFilter, + EntityDataQuery, + EntityFilter, EntityKey, TsValue } from '@shared/models/query/query.models'; @@ -36,6 +38,16 @@ import { entityFields } from '@shared/models/entity.models'; import { isUndefined } from '@core/utils'; import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; +import { + MarkAllAsReadCmd, + MarkAsReadCmd, + NotificationCountUpdateMsg, + NotificationsUpdateMsg, + UnreadCountSubCmd, + UnreadSubCmd, + UnsubscribeCmd +} from '@shared/models/websocket/notification-ws.models'; +import { Notification } from '@shared/models/notification.models'; export const NOT_SUPPORTED = 'Not supported!'; @@ -105,7 +117,7 @@ export const timeseriesDeleteStrategyTranslations = new Map; tsSubCmds: Array; @@ -290,6 +307,11 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { entityCountUnsubscribeCmds: Array; alarmCountCmds: Array; alarmCountUnsubscribeCmds: Array; + unreadNotificationsCountSubCmds: Array; + unreadNotificationsSubCmds: Array; + notificationsUnsubCmds: Array; + markNotificationAsReadCmds: Array; + markAllNotificationsAsReadCmds: Array; private static popCmds(cmds: Array, leftCount: number): Array { const toPublish = Math.min(cmds.length, leftCount); @@ -311,7 +333,12 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { this.entityCountCmds.length > 0 || this.entityCountUnsubscribeCmds.length > 0 || this.alarmCountCmds.length > 0 || - this.alarmCountUnsubscribeCmds.length > 0; + this.alarmCountUnsubscribeCmds.length > 0 || + this.unreadNotificationsCountSubCmds.length > 0 || + this.unreadNotificationsSubCmds.length > 0 || + this.notificationsUnsubCmds.length > 0 || + this.markNotificationAsReadCmds.length > 0 || + this.markAllNotificationsAsReadCmds.length > 0; } public clear() { @@ -326,6 +353,11 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { this.entityCountUnsubscribeCmds.length = 0; this.alarmCountCmds.length = 0; this.alarmCountUnsubscribeCmds.length = 0; + this.unreadNotificationsSubCmds.length = 0; + this.unreadNotificationsCountSubCmds.length = 0; + this.notificationsUnsubCmds.length = 0; + this.markNotificationAsReadCmds.length = 0; + this.markAllNotificationsAsReadCmds.length = 0; } public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper { @@ -352,6 +384,16 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { preparedWrapper.alarmCountCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountCmds, leftCount); leftCount -= preparedWrapper.alarmCountCmds.length; preparedWrapper.alarmCountUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountUnsubscribeCmds, leftCount); + leftCount -= preparedWrapper.unreadNotificationsSubCmds.length; + preparedWrapper.unreadNotificationsSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.unreadNotificationsSubCmds, leftCount); + leftCount -= preparedWrapper.unreadNotificationsCountSubCmds.length; + preparedWrapper.unreadNotificationsCountSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.unreadNotificationsCountSubCmds, leftCount); + leftCount -= preparedWrapper.notificationsUnsubCmds.length; + preparedWrapper.notificationsUnsubCmds = TelemetryPluginCmdsWrapper.popCmds(this.notificationsUnsubCmds, leftCount); + leftCount -= preparedWrapper.markNotificationAsReadCmds.length; + preparedWrapper.markNotificationAsReadCmds = TelemetryPluginCmdsWrapper.popCmds(this.markNotificationAsReadCmds, leftCount); + leftCount -= preparedWrapper.markAllNotificationsAsReadCmds.length; + preparedWrapper.markAllNotificationsAsReadCmds = TelemetryPluginCmdsWrapper.popCmds(this.markAllNotificationsAsReadCmds, leftCount); return preparedWrapper; } } @@ -416,7 +458,7 @@ export interface AlarmCountUpdateMsg extends CmdUpdateMsg { } export type WebsocketDataMsg = AlarmDataUpdateMsg | AlarmCountUpdateMsg | - EntityDataUpdateMsg | EntityCountUpdateMsg | SubscriptionUpdateMsg; + EntityDataUpdateMsg | EntityCountUpdateMsg | SubscriptionUpdateMsg | NotificationCountUpdateMsg | NotificationsUpdateMsg; export const isEntityDataUpdateMsg = (message: WebsocketDataMsg): message is EntityDataUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); @@ -627,6 +669,28 @@ export class AlarmCountUpdate extends CmdUpdate { } } +export class NotificationCountUpdate extends CmdUpdate { + totalUnreadCount: number; + + constructor(msg: NotificationCountUpdateMsg) { + super(msg); + this.totalUnreadCount = msg.totalUnreadCount; + } +} + +export class NotificationsUpdate extends CmdUpdate { + totalUnreadCount: number; + update?: Notification; + notifications?: Notification[]; + + constructor(msg: NotificationsUpdateMsg) { + super(msg); + this.totalUnreadCount = msg.totalUnreadCount; + this.update = msg.update; + this.notifications = msg.notifications; + } +} + export class TelemetrySubscriber extends WsSubscriber { private dataSubject = new ReplaySubject(1); diff --git a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts index dffa828ae1..16280fe81a 100644 --- a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts +++ b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts @@ -14,36 +14,21 @@ /// limitations under the License. /// -import { BehaviorSubject, ReplaySubject } from 'rxjs'; -import { CmdUpdate, CmdUpdateMsg, CmdUpdateType, WebsocketCmd } from '@shared/models/telemetry/telemetry.models'; -import { map } from 'rxjs/operators'; +import { + CmdUpdateMsg, + CmdUpdateType, + NotificationCountUpdate, + NotificationsUpdate, + WebsocketCmd, + WebsocketDataMsg +} from '@shared/models/telemetry/telemetry.models'; import { NgZone } from '@angular/core'; import { isDefinedAndNotNull } from '@core/utils'; import { Notification } from '@shared/models/notification.models'; -import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models'; -import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; - -export class NotificationCountUpdate extends CmdUpdate { - totalUnreadCount: number; - - constructor(msg: NotificationCountUpdateMsg) { - super(msg); - this.totalUnreadCount = msg.totalUnreadCount; - } -} - -export class NotificationsUpdate extends CmdUpdate { - totalUnreadCount: number; - update?: Notification; - notifications?: Notification[]; - - constructor(msg: NotificationsUpdateMsg) { - super(msg); - this.totalUnreadCount = msg.totalUnreadCount; - this.update = msg.update; - this.notifications = msg.notifications; - } -} +import { WsService, WsSubscriber } from '@shared/models/websocket/websocket.models'; +import { BehaviorSubject, ReplaySubject } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { WebsocketService } from '@core/ws/websocket.service'; export class NotificationSubscriber extends WsSubscriber { private notificationCountSubject = new ReplaySubject(1); @@ -61,40 +46,40 @@ export class NotificationSubscriber extends WsSubscriber { public notificationCount$ = this.notificationCountSubject.asObservable().pipe(map(msg => msg.totalUnreadCount)); public notifications$ = this.notificationsSubject.asObservable().pipe(map(msg => msg.notifications )); - public static createNotificationCountSubscription(notificationWsService: NotificationWebsocketService, + public static createNotificationCountSubscription(websocketService: WebsocketService, zone: NgZone): NotificationSubscriber { const subscriptionCommand = new UnreadCountSubCmd(); - const subscriber = new NotificationSubscriber(notificationWsService, zone); + const subscriber = new NotificationSubscriber(websocketService, zone); subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } - public static createNotificationsSubscription(notificationWsService: NotificationWebsocketService, + public static createNotificationsSubscription(websocketService: WebsocketService, zone: NgZone, limit = 10): NotificationSubscriber { const subscriptionCommand = new UnreadSubCmd(limit); - const subscriber = new NotificationSubscriber(notificationWsService, zone); + const subscriber = new NotificationSubscriber(websocketService, zone); subscriber.messageLimit = limit; subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } - public static createMarkAsReadCommand(notificationWsService: NotificationWebsocketService, + public static createMarkAsReadCommand(websocketService: WebsocketService, ids: string[]): NotificationSubscriber { const subscriptionCommand = new MarkAsReadCmd(ids); - const subscriber = new NotificationSubscriber(notificationWsService); + const subscriber = new NotificationSubscriber(websocketService); subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } - public static createMarkAllAsReadCommand(notificationWsService: NotificationWebsocketService): NotificationSubscriber { + public static createMarkAllAsReadCommand(websocketService: WebsocketService): NotificationSubscriber { const subscriptionCommand = new MarkAllAsReadCmd(); - const subscriber = new NotificationSubscriber(notificationWsService); + const subscriber = new NotificationSubscriber(websocketService); subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } - constructor(private notificationWsService: NotificationWebsocketService, protected zone?: NgZone) { - super(notificationWsService, zone); + constructor(private websocketService: WsService, protected zone?: NgZone) { + super(websocketService, zone); } onNotificationCountUpdate(message: NotificationCountUpdate) { @@ -183,58 +168,12 @@ export interface NotificationsUpdateMsg extends CmdUpdateMsg { notifications?: Notification[]; } -export type WebsocketNotificationMsg = NotificationCountUpdateMsg | NotificationsUpdateMsg; - -export const isNotificationCountUpdateMsg = (message: WebsocketNotificationMsg): message is NotificationCountUpdateMsg => { +export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT; }; -export const isNotificationsUpdateMsg = (message: WebsocketNotificationMsg): message is NotificationsUpdateMsg => { +export const isNotificationsUpdateMsg = (message: WebsocketDataMsg): message is NotificationsUpdateMsg => { const updateMsg = (message as CmdUpdateMsg); return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS; }; - -export class NotificationPluginCmdWrapper implements CmdWrapper { - - constructor() { - this.unreadCountSubCmd = null; - this.unreadSubCmd = null; - this.unsubCmd = null; - this.markAsReadCmd = null; - this.markAllAsReadCmd = null; - } - - unreadCountSubCmd: UnreadCountSubCmd; - unreadSubCmd: UnreadSubCmd; - unsubCmd: UnsubscribeCmd; - markAsReadCmd: MarkAsReadCmd; - markAllAsReadCmd: MarkAllAsReadCmd; - - public hasCommands(): boolean { - return isDefinedAndNotNull(this.unreadCountSubCmd) || - isDefinedAndNotNull(this.unreadSubCmd) || - isDefinedAndNotNull(this.unsubCmd) || - isDefinedAndNotNull(this.markAsReadCmd) || - isDefinedAndNotNull(this.markAllAsReadCmd); - } - - public clear() { - this.unreadCountSubCmd = null; - this.unreadSubCmd = null; - this.unsubCmd = null; - this.markAsReadCmd = null; - this.markAllAsReadCmd = null; - } - - public preparePublishCommands(): NotificationPluginCmdWrapper { - const preparedWrapper = new NotificationPluginCmdWrapper(); - preparedWrapper.unreadCountSubCmd = this.unreadCountSubCmd || undefined; - preparedWrapper.unreadSubCmd = this.unreadSubCmd || undefined; - preparedWrapper.unsubCmd = this.unsubCmd || undefined; - preparedWrapper.markAsReadCmd = this.markAsReadCmd || undefined; - preparedWrapper.markAllAsReadCmd = this.markAllAsReadCmd || undefined; - this.clear(); - return preparedWrapper; - } -} From 6b4c7c7ce459ab86ced9cfd394b19c8be96327db Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 7 Dec 2023 15:45:58 +0200 Subject: [PATCH 07/14] UI: Change ws command to new format and change API uri --- .../core/ws/telemetry-websocket.service.ts | 60 ++----- .../models/telemetry/telemetry.models.ts | 161 +++++------------- .../websocket/notification-ws.models.ts | 8 +- 3 files changed, 62 insertions(+), 167 deletions(-) diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 45d8510687..d89bcc2b2e 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -22,14 +22,12 @@ import { AlarmDataCmd, AlarmDataUnsubscribeCmd, AlarmDataUpdate, - AttributesSubscriptionCmd, EntityCountCmd, EntityCountUnsubscribeCmd, EntityCountUpdate, EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate, - GetHistoryCmd, isAlarmCountUpdateMsg, isAlarmDataUpdateMsg, isEntityCountUpdateMsg, @@ -38,10 +36,8 @@ import { NotificationsUpdate, SubscriptionCmd, SubscriptionUpdate, - TelemetryFeature, TelemetryPluginCmdsWrapper, TelemetrySubscriber, - TimeseriesSubscriptionCmd, WebsocketDataMsg } from '@app/shared/models/telemetry/telemetry.models'; import { Store } from '@ngrx/store'; @@ -72,7 +68,7 @@ export class TelemetryWebsocketService extends WebsocketService { const cmdId = this.nextCmdId(); - this.subscribersMap.set(cmdId, subscriber); - subscriptionCommand.cmdId = cmdId; - if (subscriptionCommand instanceof SubscriptionCmd) { - if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) { - this.cmdWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd); - } else { - this.cmdWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd); - } - } else if (subscriptionCommand instanceof GetHistoryCmd) { - this.cmdWrapper.historyCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof EntityDataCmd) { - this.cmdWrapper.entityDataCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof AlarmDataCmd) { - this.cmdWrapper.alarmDataCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof EntityCountCmd) { - this.cmdWrapper.entityCountCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof AlarmCountCmd) { - this.cmdWrapper.alarmCountCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof UnreadCountSubCmd) { - this.cmdWrapper.unreadNotificationsCountSubCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof UnreadSubCmd) { - this.cmdWrapper.unreadNotificationsSubCmds.push(subscriptionCommand); - } else if (subscriptionCommand instanceof MarkAsReadCmd) { - this.cmdWrapper.markNotificationAsReadCmds.push(subscriptionCommand); - this.subscribersMap.delete(cmdId); - } else if (subscriptionCommand instanceof MarkAllAsReadCmd) { - this.cmdWrapper.markAllNotificationsAsReadCmds.push(subscriptionCommand); - this.subscribersMap.delete(cmdId); + if (!(subscriptionCommand instanceof MarkAsReadCmd) && !(subscriptionCommand instanceof MarkAllAsReadCmd)) { + this.subscribersMap.set(cmdId, subscriber); } + subscriptionCommand.cmdId = cmdId; + this.cmdWrapper.cmds.push(subscriptionCommand); } ); this.subscribersCount++; @@ -119,10 +91,8 @@ export class TelemetryWebsocketService extends WebsocketService { - if (subscriptionCommand.cmdId && subscriptionCommand instanceof EntityDataCmd) { - this.cmdWrapper.entityDataCmds.push(subscriptionCommand); - } else if (subscriptionCommand.cmdId && subscriptionCommand instanceof UnreadSubCmd) { - this.cmdWrapper.unreadNotificationsSubCmds.push(subscriptionCommand); + if (subscriptionCommand.cmdId && (subscriptionCommand instanceof EntityDataCmd || subscriptionCommand instanceof UnreadSubCmd)) { + this.cmdWrapper.cmds.push(subscriptionCommand); } } ); @@ -136,31 +106,27 @@ export class TelemetryWebsocketService extends WebsocketService { if (subscriptionCommand instanceof SubscriptionCmd) { subscriptionCommand.unsubscribe = true; - if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) { - this.cmdWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd); - } else { - this.cmdWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd); - } + this.cmdWrapper.cmds.push(subscriptionCommand); } else if (subscriptionCommand instanceof EntityDataCmd) { const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd(); entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.entityDataUnsubscribeCmds.push(entityDataUnsubscribeCmd); + this.cmdWrapper.cmds.push(entityDataUnsubscribeCmd); } else if (subscriptionCommand instanceof AlarmDataCmd) { const alarmDataUnsubscribeCmd = new AlarmDataUnsubscribeCmd(); alarmDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.alarmDataUnsubscribeCmds.push(alarmDataUnsubscribeCmd); + this.cmdWrapper.cmds.push(alarmDataUnsubscribeCmd); } else if (subscriptionCommand instanceof EntityCountCmd) { const entityCountUnsubscribeCmd = new EntityCountUnsubscribeCmd(); entityCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.entityCountUnsubscribeCmds.push(entityCountUnsubscribeCmd); + this.cmdWrapper.cmds.push(entityCountUnsubscribeCmd); } else if (subscriptionCommand instanceof AlarmCountCmd) { const alarmCountUnsubscribeCmd = new AlarmCountUnsubscribeCmd(); alarmCountUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.alarmCountUnsubscribeCmds.push(alarmCountUnsubscribeCmd); + this.cmdWrapper.cmds.push(alarmCountUnsubscribeCmd); } else if (subscriptionCommand instanceof UnreadCountSubCmd || subscriptionCommand instanceof UnreadSubCmd) { const notificationsUnsubCmds = new UnsubscribeCmd(); notificationsUnsubCmds.cmdId = subscriptionCommand.cmdId; - this.cmdWrapper.notificationsUnsubCmds.push(notificationsUnsubCmds); + this.cmdWrapper.cmds.push(notificationsUnsubCmds); } const cmdId = subscriptionCommand.cmdId; if (cmdId) { diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 57e94baef2..671b503c92 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -38,15 +38,7 @@ import { entityFields } from '@shared/models/entity.models'; import { isUndefined } from '@core/utils'; import { CmdWrapper, WsSubscriber } from '@shared/models/websocket/websocket.models'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; -import { - MarkAllAsReadCmd, - MarkAsReadCmd, - NotificationCountUpdateMsg, - NotificationsUpdateMsg, - UnreadCountSubCmd, - UnreadSubCmd, - UnsubscribeCmd -} from '@shared/models/websocket/notification-ws.models'; +import { NotificationCountUpdateMsg, NotificationsUpdateMsg } from '@shared/models/websocket/notification-ws.models'; import { Notification } from '@shared/models/notification.models'; export const NOT_SUPPORTED = 'Not supported!'; @@ -70,11 +62,6 @@ export enum AttributeScope { SHARED_SCOPE = 'SHARED_SCOPE' } -export enum TelemetryFeature { - ATTRIBUTES = 'ATTRIBUTES', - TIMESERIES = 'TIMESERIES' -} - export enum TimeseriesDeleteStrategy { DELETE_ALL_DATA = 'DELETE_ALL_DATA', DELETE_ALL_DATA_EXCEPT_LATEST_VALUE = 'DELETE_ALL_DATA_EXCEPT_LATEST_VALUE', @@ -134,8 +121,30 @@ export enum DataSortOrder { DESC = 'DESC' } +export enum WsCmdType { + ATTRIBUTES = 'ATTRIBUTES', + TIMESERIES = 'TIMESERIES', + TIMESERIES_HISTORY = 'TIMESERIES_HISTORY', + ENTITY_DATA = 'ENTITY_DATA', + ENTITY_COUNT = 'ENTITY_COUNT', + ALARM_DATA = 'ALARM_DATA', + ALARM_COUNT = 'ALARM_COUNT', + + NOTIFICATIONS = 'NOTIFICATIONS', + NOTIFICATIONS_COUNT = 'NOTIFICATIONS_COUNT', + MARK_NOTIFICATIONS_AS_READ = 'MARK_NOTIFICATIONS_AS_READ', + MARK_ALL_NOTIFICATIONS_AS_READ = 'MARK_ALL_NOTIFICATIONS_AS_READ', + + ALARM_DATA_UNSUBSCRIBE = 'ALARM_DATA_UNSUBSCRIBE', + ALARM_COUNT_UNSUBSCRIBE = 'ALARM_COUNT_UNSUBSCRIBE', + ENTITY_DATA_UNSUBSCRIBE = 'ENTITY_DATA_UNSUBSCRIBE', + ENTITY_COUNT_UNSUBSCRIBE = 'ENTITY_COUNT_UNSUBSCRIBE', + NOTIFICATIONS_UNSUBSCRIBE = 'NOTIFICATIONS_UNSUBSCRIBE' +} + export interface WebsocketCmd { cmdId: number; + type: WsCmdType; } export interface TelemetryPluginCmd extends WebsocketCmd { @@ -149,13 +158,11 @@ export abstract class SubscriptionCmd implements TelemetryPluginCmd { entityId: string; scope?: AttributeScope; unsubscribe: boolean; - abstract getType(): TelemetryFeature; + abstract type: WsCmdType; } export class AttributesSubscriptionCmd extends SubscriptionCmd { - getType() { - return TelemetryFeature.ATTRIBUTES; - } + type = WsCmdType.ATTRIBUTES; } export class TimeseriesSubscriptionCmd extends SubscriptionCmd { @@ -164,10 +171,7 @@ export class TimeseriesSubscriptionCmd extends SubscriptionCmd { interval: number; limit: number; agg: AggregationType; - - getType() { - return TelemetryFeature.TIMESERIES; - } + type = WsCmdType.TIMESERIES; } export class GetHistoryCmd implements TelemetryPluginCmd { @@ -180,6 +184,7 @@ export class GetHistoryCmd implements TelemetryPluginCmd { interval: number; limit: number; agg: AggregationType; + type = WsCmdType.TIMESERIES_HISTORY; } export interface EntityHistoryCmd { @@ -235,6 +240,7 @@ export class EntityDataCmd implements WebsocketCmd { tsCmd?: TimeSeriesCmd; aggHistoryCmd?: AggEntityHistoryCmd; aggTsCmd?: AggTimeSeriesCmd; + type = WsCmdType.ENTITY_DATA; public isEmpty(): boolean { return !this.query && !this.historyCmd && !this.latestCmd && !this.tsCmd && !this.aggTsCmd && !this.aggHistoryCmd; @@ -244,11 +250,13 @@ export class EntityDataCmd implements WebsocketCmd { export class EntityCountCmd implements WebsocketCmd { cmdId: number; query?: EntityCountQuery; + type = WsCmdType.ENTITY_COUNT; } export class AlarmDataCmd implements WebsocketCmd { cmdId: number; query?: AlarmDataQuery; + type = WsCmdType.ALARM_DATA; public isEmpty(): boolean { return !this.query; @@ -258,60 +266,36 @@ export class AlarmDataCmd implements WebsocketCmd { export class AlarmCountCmd implements WebsocketCmd { cmdId: number; query?: AlarmCountQuery; + type = WsCmdType.ALARM_COUNT; } export class EntityDataUnsubscribeCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.ENTITY_DATA_UNSUBSCRIBE; } export class EntityCountUnsubscribeCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.ENTITY_COUNT_UNSUBSCRIBE; } export class AlarmDataUnsubscribeCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.ALARM_DATA_UNSUBSCRIBE; } export class AlarmCountUnsubscribeCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.ALARM_COUNT_UNSUBSCRIBE; } export class TelemetryPluginCmdsWrapper implements CmdWrapper { constructor() { - this.attrSubCmds = []; - this.tsSubCmds = []; - this.historyCmds = []; - this.entityDataCmds = []; - this.entityDataUnsubscribeCmds = []; - this.alarmDataCmds = []; - this.alarmDataUnsubscribeCmds = []; - this.entityCountCmds = []; - this.entityCountUnsubscribeCmds = []; - this.alarmCountCmds = []; - this.alarmCountUnsubscribeCmds = []; - this.unreadNotificationsCountSubCmds = []; - this.unreadNotificationsSubCmds = []; - this.notificationsUnsubCmds = []; - this.markNotificationAsReadCmds = []; - this.markAllNotificationsAsReadCmds = []; - } - attrSubCmds: Array; - tsSubCmds: Array; - historyCmds: Array; - entityDataCmds: Array; - entityDataUnsubscribeCmds: Array; - alarmDataCmds: Array; - alarmDataUnsubscribeCmds: Array; - entityCountCmds: Array; - entityCountUnsubscribeCmds: Array; - alarmCountCmds: Array; - alarmCountUnsubscribeCmds: Array; - unreadNotificationsCountSubCmds: Array; - unreadNotificationsSubCmds: Array; - notificationsUnsubCmds: Array; - markNotificationAsReadCmds: Array; - markAllNotificationsAsReadCmds: Array; + this.cmds = []; + } + + cmds: Array; private static popCmds(cmds: Array, leftCount: number): Array { const toPublish = Math.min(cmds.length, leftCount); @@ -323,77 +307,16 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { } public hasCommands(): boolean { - return this.tsSubCmds.length > 0 || - this.historyCmds.length > 0 || - this.attrSubCmds.length > 0 || - this.entityDataCmds.length > 0 || - this.entityDataUnsubscribeCmds.length > 0 || - this.alarmDataCmds.length > 0 || - this.alarmDataUnsubscribeCmds.length > 0 || - this.entityCountCmds.length > 0 || - this.entityCountUnsubscribeCmds.length > 0 || - this.alarmCountCmds.length > 0 || - this.alarmCountUnsubscribeCmds.length > 0 || - this.unreadNotificationsCountSubCmds.length > 0 || - this.unreadNotificationsSubCmds.length > 0 || - this.notificationsUnsubCmds.length > 0 || - this.markNotificationAsReadCmds.length > 0 || - this.markAllNotificationsAsReadCmds.length > 0; + return this.cmds.length > 0; } public clear() { - this.attrSubCmds.length = 0; - this.tsSubCmds.length = 0; - this.historyCmds.length = 0; - this.entityDataCmds.length = 0; - this.entityDataUnsubscribeCmds.length = 0; - this.alarmDataCmds.length = 0; - this.alarmDataUnsubscribeCmds.length = 0; - this.entityCountCmds.length = 0; - this.entityCountUnsubscribeCmds.length = 0; - this.alarmCountCmds.length = 0; - this.alarmCountUnsubscribeCmds.length = 0; - this.unreadNotificationsSubCmds.length = 0; - this.unreadNotificationsCountSubCmds.length = 0; - this.notificationsUnsubCmds.length = 0; - this.markNotificationAsReadCmds.length = 0; - this.markAllNotificationsAsReadCmds.length = 0; + this.cmds.length = 0; } public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper { const preparedWrapper = new TelemetryPluginCmdsWrapper(); - let leftCount = maxCommands; - preparedWrapper.tsSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.tsSubCmds, leftCount); - leftCount -= preparedWrapper.tsSubCmds.length; - preparedWrapper.historyCmds = TelemetryPluginCmdsWrapper.popCmds(this.historyCmds, leftCount); - leftCount -= preparedWrapper.historyCmds.length; - preparedWrapper.attrSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.attrSubCmds, leftCount); - leftCount -= preparedWrapper.attrSubCmds.length; - preparedWrapper.entityDataCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityDataCmds, leftCount); - leftCount -= preparedWrapper.entityDataCmds.length; - preparedWrapper.entityDataUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityDataUnsubscribeCmds, leftCount); - leftCount -= preparedWrapper.entityDataUnsubscribeCmds.length; - preparedWrapper.alarmDataCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmDataCmds, leftCount); - leftCount -= preparedWrapper.alarmDataCmds.length; - preparedWrapper.alarmDataUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmDataUnsubscribeCmds, leftCount); - leftCount -= preparedWrapper.alarmDataUnsubscribeCmds.length; - preparedWrapper.entityCountCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityCountCmds, leftCount); - leftCount -= preparedWrapper.entityCountCmds.length; - preparedWrapper.entityCountUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.entityCountUnsubscribeCmds, leftCount); - leftCount -= preparedWrapper.entityCountUnsubscribeCmds.length; - preparedWrapper.alarmCountCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountCmds, leftCount); - leftCount -= preparedWrapper.alarmCountCmds.length; - preparedWrapper.alarmCountUnsubscribeCmds = TelemetryPluginCmdsWrapper.popCmds(this.alarmCountUnsubscribeCmds, leftCount); - leftCount -= preparedWrapper.unreadNotificationsSubCmds.length; - preparedWrapper.unreadNotificationsSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.unreadNotificationsSubCmds, leftCount); - leftCount -= preparedWrapper.unreadNotificationsCountSubCmds.length; - preparedWrapper.unreadNotificationsCountSubCmds = TelemetryPluginCmdsWrapper.popCmds(this.unreadNotificationsCountSubCmds, leftCount); - leftCount -= preparedWrapper.notificationsUnsubCmds.length; - preparedWrapper.notificationsUnsubCmds = TelemetryPluginCmdsWrapper.popCmds(this.notificationsUnsubCmds, leftCount); - leftCount -= preparedWrapper.markNotificationAsReadCmds.length; - preparedWrapper.markNotificationAsReadCmds = TelemetryPluginCmdsWrapper.popCmds(this.markNotificationAsReadCmds, leftCount); - leftCount -= preparedWrapper.markAllNotificationsAsReadCmds.length; - preparedWrapper.markAllNotificationsAsReadCmds = TelemetryPluginCmdsWrapper.popCmds(this.markAllNotificationsAsReadCmds, leftCount); + preparedWrapper.cmds = TelemetryPluginCmdsWrapper.popCmds(this.cmds, maxCommands); return preparedWrapper; } } diff --git a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts index 16280fe81a..ea85a7ffcc 100644 --- a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts +++ b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts @@ -20,7 +20,8 @@ import { NotificationCountUpdate, NotificationsUpdate, WebsocketCmd, - WebsocketDataMsg + WebsocketDataMsg, + WsCmdType } from '@shared/models/telemetry/telemetry.models'; import { NgZone } from '@angular/core'; import { isDefinedAndNotNull } from '@core/utils'; @@ -127,11 +128,13 @@ export class NotificationSubscriber extends WsSubscriber { export class UnreadCountSubCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.NOTIFICATIONS_COUNT; } export class UnreadSubCmd implements WebsocketCmd { limit: number; cmdId: number; + type = WsCmdType.NOTIFICATIONS; constructor(limit = 10) { this.limit = limit; @@ -140,12 +143,14 @@ export class UnreadSubCmd implements WebsocketCmd { export class UnsubscribeCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.NOTIFICATIONS_UNSUBSCRIBE; } export class MarkAsReadCmd implements WebsocketCmd { cmdId: number; notifications: string[]; + type = WsCmdType.MARK_NOTIFICATIONS_AS_READ; constructor(ids: string[]) { this.notifications = ids; @@ -154,6 +159,7 @@ export class MarkAsReadCmd implements WebsocketCmd { export class MarkAllAsReadCmd implements WebsocketCmd { cmdId: number; + type = WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ; } export interface NotificationCountUpdateMsg extends CmdUpdateMsg { From c21f0d025c9f312dbbd83ad34bbb9b0ceda9dcc2 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 8 Dec 2023 15:15:15 +0200 Subject: [PATCH 08/14] WS auth via command --- .../ThingsboardSecurityConfiguration.java | 24 +-- .../server/config/WebSocketConfiguration.java | 46 +---- .../controller/plugin/TbWebSocketHandler.java | 166 ++++++++++++++---- .../auth/DefaultTokenOutdatingService.java | 3 +- .../security/auth/TokenOutdatingService.java | 3 +- .../auth/jwt/JwtAuthenticationProvider.java | 16 +- .../RefreshTokenAuthenticationProvider.java | 4 +- .../exception/JwtExpiredTokenException.java | 7 +- .../security/model/token/JwtTokenFactory.java | 12 +- .../subscription/TbAbstractSubCtx.java | 2 +- .../server/service/ws/AuthCmd.java | 33 ++++ .../service/ws/DefaultWebSocketService.java | 102 ++++------- .../server/service/ws/WebSocketService.java | 11 +- .../service/ws/WebSocketSessionRef.java | 8 +- .../server/service/ws/WsCmdType.java | 2 + .../server/service/ws/WsCommandsWrapper.java | 2 + .../DefaultNotificationCommandsHandler.java | 2 +- .../cmd/NotificationCmdsWrapper.java | 2 +- .../telemetry/cmd/TelemetryCmdsWrapper.java | 2 +- .../ws/telemetry/cmd/v2/AuthCmdUpdate.java | 34 ++++ .../ws/telemetry/cmd/v2/CmdUpdateType.java | 3 +- .../controller/AbstractControllerTest.java | 5 +- .../controller/TbTestWebSocketClient.java | 8 + .../server/controller/WebsocketApiTest.java | 2 +- .../AbstractNotificationApiTest.java | 3 +- .../notification/NotificationApiWsClient.java | 4 +- .../security/auth/JwtTokenFactoryTest.java | 8 +- .../security/auth/TokenOutdatingTest.java | 4 +- 28 files changed, 312 insertions(+), 206 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java create mode 100644 application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 55370b04b8..6b29ba0b28 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -32,14 +32,12 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.header.writers.StaticHeadersWriter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.ShallowEtagHeaderFilter; @@ -63,7 +61,7 @@ import java.util.List; @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled=true) +@EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.BASIC_AUTH_ORDER) @TbCoreComponent public class ThingsboardSecurityConfiguration { @@ -79,7 +77,7 @@ public class ThingsboardSecurityConfiguration { public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"}; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; - public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; + public static final String WS_ENTRY_POINT = "/api/ws/**"; public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code"; public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download"; @@ -115,10 +113,6 @@ public class ThingsboardSecurityConfiguration { @Qualifier("jwtHeaderTokenExtractor") private TokenExtractor jwtHeaderTokenExtractor; - @Autowired - @Qualifier("jwtQueryTokenExtractor") - private TokenExtractor jwtQueryTokenExtractor; - @Autowired private AuthenticationManager authenticationManager; @Autowired private RateLimitProcessingFilter rateLimitProcessingFilter; @@ -150,7 +144,7 @@ public class ThingsboardSecurityConfiguration { protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); - pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, + pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT, DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT)); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); @@ -167,15 +161,6 @@ public class ThingsboardSecurityConfiguration { return filter; } - @Bean - protected JwtTokenAuthenticationProcessingFilter buildWsJwtTokenAuthenticationProcessingFilter() throws Exception { - AntPathRequestMatcher matcher = new AntPathRequestMatcher(WS_TOKEN_BASED_AUTH_ENTRY_POINT); - JwtTokenAuthenticationProcessingFilter filter - = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtQueryTokenExtractor, matcher); - filter.setAuthenticationManager(this.authenticationManager); - return filter; - } - @Bean public AuthenticationManager authenticationManager(ObjectPostProcessor objectPostProcessor) throws Exception { DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor @@ -229,7 +214,7 @@ public class ThingsboardSecurityConfiguration { .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points .and() .authorizeRequests() - .antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points + .antMatchers(WS_ENTRY_POINT).permitAll() // WebSocket API End-points .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points .and() .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) @@ -238,7 +223,6 @@ public class ThingsboardSecurityConfiguration { .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class); if (oauth2Configuration != null) { http.oauth2Login() diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java index e6097ec5d2..467e20adb8 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java @@ -19,25 +19,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; -import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; -import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.controller.plugin.TbWebSocketHandler; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.model.SecurityUser; - -import java.util.Map; @Configuration @TbCoreComponent @@ -66,39 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { log.error("TbWebSocketHandler expected but [{}] provided", wsHandler); throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided"); } - registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*") - .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() { - - @Override - public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, - Map attributes) throws Exception { - SecurityUser user = null; - try { - user = getCurrentUser(); - } catch (ThingsboardException ex) { - } - if (user == null) { - response.setStatusCode(HttpStatus.UNAUTHORIZED); - return false; - } else { - return true; - } - } - - @Override - public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, - Exception exception) { - //Do nothing - } - }); + registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*"); } - protected SecurityUser getCurrentUser() throws ThingsboardException { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) { - return (SecurityUser) authentication.getPrincipal(); - } else { - throw new ThingsboardException("You aren't authorized to perform this operation!", ThingsboardErrorCode.AUTHENTICATION); - } - } } diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index de073aca9a..855cbbc1e5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -15,13 +15,16 @@ */ package org.thingsboard.server.controller.plugin; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.PongMessage; @@ -29,6 +32,7 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.id.CustomerId; @@ -40,13 +44,20 @@ import org.thingsboard.server.config.WebSocketConfiguration; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.util.limits.RateLimitService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; +import org.thingsboard.server.service.ws.AuthCmd; import org.thingsboard.server.service.ws.SessionEvent; import org.thingsboard.server.service.ws.WebSocketMsgEndpoint; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; import org.thingsboard.server.service.ws.WebSocketSessionType; +import org.thingsboard.server.service.ws.WsCommandsWrapper; +import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; +import org.thingsboard.server.service.ws.telemetry.cmd.v2.AuthCmdUpdate; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; @@ -61,6 +72,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @@ -68,20 +80,20 @@ import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_O @Service @TbCoreComponent @Slf4j +@RequiredArgsConstructor public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint { private final ConcurrentMap internalSessionMap = new ConcurrentHashMap<>(); private final ConcurrentMap externalSessionMap = new ConcurrentHashMap<>(); - @Autowired @Lazy private WebSocketService webSocketService; - @Autowired private TbTenantProfileCache tenantProfileCache; - @Autowired private RateLimitService rateLimitService; + @Autowired + private JwtAuthenticationProvider authenticationProvider; @Value("${server.ws.send_timeout:5000}") private long sendTimeout; @@ -97,16 +109,77 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private final ConcurrentMap> regularUserSessionsMap = new ConcurrentHashMap<>(); private final ConcurrentMap> publicUserSessionsMap = new ConcurrentHashMap<>(); + private final Cache pendingSessions = Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.SECONDS) + .removalListener((sessionId, sessionMd, removalCause) -> { + if (removalCause == RemovalCause.EXPIRED && sessionMd != null) { + try { + close(sessionMd.sessionRef, CloseStatus.POLICY_VIOLATION); + } catch (IOException e) { + log.warn("IO error", e); + } + } + }) + .build(); + @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { try { - SessionMetaData sessionMd = internalSessionMap.get(session.getId()); - if (sessionMd != null) { - log.trace("[{}][{}] Processing {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload()); - webSocketService.handleWebSocketMsg(sessionMd.sessionRef, message.getPayload()); - } else { + SessionMetaData sessionMd = getSessionMd(session.getId()); + if (sessionMd == null) { log.trace("[{}] Failed to find session", session.getId()); session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!")); + return; + } + WebSocketSessionRef sessionRef = sessionMd.sessionRef; + String msg = message.getPayload(); + + WsCommandsWrapper cmdsWrapper; + try { + switch (sessionRef.getSessionType()) { + case GENERAL: + cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); + break; + case TELEMETRY: + cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper(); + break; + case NOTIFICATIONS: + cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper(); + break; + default: + return; + } + } catch (Exception e) { + log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); + if (sessionRef.getSecurityCtx() != null) { + webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload"); + } else { + close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); + } + return; + } + + if (sessionRef.getSecurityCtx() != null) { + log.trace("[{}][{}] Processing {}", sessionRef.getSecurityCtx().getTenantId(), session.getId(), msg); + webSocketService.handleCommands(sessionRef, cmdsWrapper); + } else { + AuthCmd authCmd = cmdsWrapper.getAuthCmd(); + if (authCmd == null) { + close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing")); + return; + } + log.trace("[{}] Authenticating session", session.getId()); + SecurityUser securityCtx; + try { + securityCtx = authenticationProvider.authenticate(authCmd.getToken()); + } catch (Exception e) { + close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); + return; + } + sessionRef.setSecurityCtx(securityCtx); + pendingSessions.invalidate(session.getId()); + establishSession(session, sessionRef); + webSocketService.sendUpdate(sessionRef.getSessionId(), new AuthCmdUpdate(1)); } } catch (IOException e) { log.warn("IO error", e); @@ -116,7 +189,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke @Override protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception { try { - SessionMetaData sessionMd = internalSessionMap.get(session.getId()); + SessionMetaData sessionMd = getSessionMd(session.getId()); if (sessionMd != null) { log.trace("[{}][{}] Processing pong response {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload()); sessionMd.processPongMessage(System.currentTimeMillis()); @@ -139,36 +212,45 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke nativeSession.getAsyncRemote().setSendTimeout(sendTimeout); } } - String internalSessionId = session.getId(); WebSocketSessionRef sessionRef = toRef(session); - String externalSessionId = sessionRef.getSessionId(); + log.debug("[{}][{}] Session opened from address: {}", sessionRef.getSessionId(), session.getId(), session.getRemoteAddress()); + establishSession(session, sessionRef); + } catch (InvalidParameterException e) { + log.warn("[{}] Failed to start session", session.getId(), e); + session.close(CloseStatus.BAD_DATA.withReason(e.getMessage())); + } catch (Exception e) { + log.warn("[{}] Failed to start session", session.getId(), e); + session.close(CloseStatus.SERVER_ERROR.withReason(e.getMessage())); + } + } + private void establishSession(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException { + if (sessionRef.getSecurityCtx() != null) { if (!checkLimits(session, sessionRef)) { return; } var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); int wsTenantProfileQueueLimit = tenantProfileConfiguration != null ? tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : wsMaxQueueMessagesPerSession; - internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, + SessionMetaData sessionMd = new SessionMetaData(session, sessionRef, (wsTenantProfileQueueLimit > 0 && wsTenantProfileQueueLimit < wsMaxQueueMessagesPerSession) ? - wsTenantProfileQueueLimit : wsMaxQueueMessagesPerSession)); + wsTenantProfileQueueLimit : wsMaxQueueMessagesPerSession); - externalSessionMap.put(externalSessionId, internalSessionId); + internalSessionMap.put(session.getId(), sessionMd); + externalSessionMap.put(sessionRef.getSessionId(), session.getId()); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); - log.info("[{}][{}][{}] Session is opened from address: {}", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId(), session.getRemoteAddress()); - } catch (InvalidParameterException e) { - log.warn("[{}] Failed to start session", session.getId(), e); - session.close(CloseStatus.BAD_DATA.withReason(e.getMessage())); - } catch (Exception e) { - log.warn("[{}] Failed to start session", session.getId(), e); - session.close(CloseStatus.SERVER_ERROR.withReason(e.getMessage())); + log.info("[{}][{}][{}] Session established from address: {}", sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSessionId(), session.getId(), session.getRemoteAddress()); + } else { + SessionMetaData sessionMd = new SessionMetaData(session, sessionRef, wsMaxQueueMessagesPerSession); + pendingSessions.put(session.getId(), sessionMd); + externalSessionMap.put(sessionRef.getSessionId(), session.getId()); } } @Override public void handleTransportError(WebSocketSession session, Throwable tError) throws Exception { super.handleTransportError(session, tError); - SessionMetaData sessionMd = internalSessionMap.get(session.getId()); + SessionMetaData sessionMd = getSessionMd(session.getId()); if (sessionMd != null) { processInWebSocketService(sessionMd.sessionRef, SessionEvent.onError(tError)); } else { @@ -181,10 +263,15 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { super.afterConnectionClosed(session, closeStatus); SessionMetaData sessionMd = internalSessionMap.remove(session.getId()); + if (sessionMd == null) { + sessionMd = pendingSessions.asMap().remove(session.getId()); + } if (sessionMd != null) { - cleanupLimits(session, sessionMd.sessionRef); externalSessionMap.remove(sessionMd.sessionRef.getSessionId()); - processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed()); + if (sessionMd.sessionRef.getSecurityCtx() != null) { + cleanupLimits(session, sessionMd.sessionRef); + processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed()); + } log.info("[{}][{}][{}] Session is closed", sessionMd.sessionRef.getSecurityCtx().getTenantId(), sessionMd.sessionRef.getSessionId(), session.getId()); } else { log.info("[{}] Session is closed", session.getId()); @@ -192,8 +279,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) { + if (sessionRef.getSecurityCtx() == null) { + return; + } try { - webSocketService.handleWebSocketSessionEvent(sessionRef, event); + webSocketService.handleSessionEvent(sessionRef, event); } catch (BeanCreationNotAllowedException e) { log.warn("[{}] Failed to close session due to possible shutdown state", sessionRef.getSessionId()); } @@ -210,16 +300,28 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke .orElseThrow(() -> new InvalidParameterException("Unknown session type")); } - SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal(); + SecurityUser securityCtx = null; + String token = StringUtils.substringAfter(session.getUri().getQuery(), "token="); + if (StringUtils.isNotEmpty(token)) { + securityCtx = authenticationProvider.authenticate(token); + } return WebSocketSessionRef.builder() .sessionId(UUID.randomUUID().toString()) - .securityCtx(currentUser) + .securityCtx(securityCtx) .localAddress(session.getLocalAddress()) .remoteAddress(session.getRemoteAddress()) .sessionType(sessionType) .build(); } + private SessionMetaData getSessionMd(String internalSessionId) { + SessionMetaData sessionMd = internalSessionMap.get(internalSessionId); + if (sessionMd == null) { + sessionMd = pendingSessions.getIfPresent(internalSessionId); + } + return sessionMd; + } + class SessionMetaData implements SendHandler { private final WebSocketSession session; private final RemoteEndpoint.Async asyncRemote; @@ -228,6 +330,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke final AtomicBoolean isSending = new AtomicBoolean(false); private final Queue> msgQueue; + // TODO: msg queue as in org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx + private volatile long lastActivityTime; SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { @@ -335,7 +439,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke @Override public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException { String externalId = sessionRef.getSessionId(); - log.debug("[{}] Processing {}", externalId, msg); + log.debug("[{}] Sending {}", externalId, msg); String internalId = externalSessionMap.get(externalId); if (internalId != null) { SessionMetaData sessionMd = internalSessionMap.get(internalId); @@ -383,7 +487,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke log.debug("[{}] Processing close request", externalId); String internalId = externalSessionMap.get(externalId); if (internalId != null) { - SessionMetaData sessionMd = internalSessionMap.get(internalId); + SessionMetaData sessionMd = getSessionMd(internalId); if (sessionMd != null) { sessionMd.session.close(reason); } else { @@ -394,7 +498,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception { + private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException { var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); if (tenantProfileConfiguration == null) { return true; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java index 9f33466793..02c712a640 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java @@ -23,7 +23,6 @@ import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; -import org.thingsboard.server.common.data.security.model.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import java.util.Optional; @@ -49,7 +48,7 @@ public class DefaultTokenOutdatingService implements TokenOutdatingService { } @Override - public boolean isOutdated(JwtToken token, UserId userId) { + public boolean isOutdated(String token, UserId userId) { Claims claims = tokenFactory.parseTokenClaims(token).getBody(); long issueTime = claims.getIssuedAt().getTime(); String sessionId = claims.get("sessionId", String.class); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java index 20df639619..f1d0c499ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java @@ -16,10 +16,9 @@ package org.thingsboard.server.service.security.auth; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.model.JwtToken; public interface TokenOutdatingService { - boolean isOutdated(JwtToken token, UserId userId); + boolean isOutdated(String token, UserId userId); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java index e0242126fd..43389e2a09 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java @@ -16,7 +16,9 @@ package org.thingsboard.server.service.security.auth.jwt; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; @@ -37,13 +39,19 @@ public class JwtAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); - SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken); + SecurityUser securityUser = authenticate(rawAccessToken.getToken()); + return new JwtAuthenticationToken(securityUser); + } - if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) { + public SecurityUser authenticate(String accessToken) throws AuthenticationException { + if (StringUtils.isEmpty(accessToken)) { + throw new BadCredentialsException("Token is invalid"); + } + SecurityUser securityUser = tokenFactory.parseAccessJwtToken(accessToken); + if (tokenOutdatingService.isOutdated(accessToken, securityUser.getId())) { throw new JwtExpiredTokenException("Token is outdated"); } - - return new JwtAuthenticationToken(securityUser); + return securityUser; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java index e75d3aa4d1..527ff97ba5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java @@ -57,7 +57,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.notNull(authentication, "No authentication data provided"); RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); - SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken); + SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken.getToken()); UserPrincipal principal = unsafeUser.getUserPrincipal(); SecurityUser securityUser; @@ -67,7 +67,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide securityUser = authenticateByPublicId(principal.getValue()); } securityUser.setSessionId(unsafeUser.getSessionId()); - if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) { + if (tokenOutdatingService.isOutdated(rawAccessToken.getToken(), securityUser.getId())) { throw new CredentialsExpiredException("Token is outdated"); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java index 87b7403a85..4e4430ceaa 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java +++ b/application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java @@ -16,23 +16,22 @@ package org.thingsboard.server.service.security.exception; import org.springframework.security.core.AuthenticationException; -import org.thingsboard.server.common.data.security.model.JwtToken; public class JwtExpiredTokenException extends AuthenticationException { private static final long serialVersionUID = -5959543783324224864L; - private JwtToken token; + private String token; public JwtExpiredTokenException(String msg) { super(msg); } - public JwtExpiredTokenException(JwtToken token, String msg, Throwable t) { + public JwtExpiredTokenException(String token, String msg, Throwable t) { super(msg, t); this.token = token; } public String token() { - return this.token.getToken(); + return this.token; } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index a222a7e83a..a9d32c4d95 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -93,8 +93,8 @@ public class JwtTokenFactory { return new AccessJwtToken(token); } - public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) { - Jws jwsClaims = parseTokenClaims(rawAccessToken); + public SecurityUser parseAccessJwtToken(String token) { + Jws jwsClaims = parseTokenClaims(token); Claims claims = jwsClaims.getBody(); String subject = claims.getSubject(); @SuppressWarnings("unchecked") @@ -145,8 +145,8 @@ public class JwtTokenFactory { return new AccessJwtToken(token); } - public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) { - Jws jwsClaims = parseTokenClaims(rawAccessToken); + public SecurityUser parseRefreshToken(String token) { + Jws jwsClaims = parseTokenClaims(token); Claims claims = jwsClaims.getBody(); String subject = claims.getSubject(); @SuppressWarnings("unchecked") @@ -200,11 +200,11 @@ public class JwtTokenFactory { .signWith(SignatureAlgorithm.HS512, jwtSettingsService.getJwtSettings().getTokenSigningKey()); } - public Jws parseTokenClaims(JwtToken token) { + public Jws parseTokenClaims(String token) { try { return Jwts.parser() .setSigningKey(jwtSettingsService.getJwtSettings().getTokenSigningKey()) - .parseClaimsJws(token.getToken()); + .parseClaimsJws(token); } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) { log.debug("Invalid JWT Token", ex); throw new BadCredentialsException("Invalid JWT token: ", ex); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java index 102431f63b..b4468c0bd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java @@ -336,7 +336,7 @@ public abstract class TbAbstractSubCtx { public void sendWsMsg(CmdUpdate update) { wsLock.lock(); try { - wsService.sendWsMsg(sessionRef.getSessionId(), update); + wsService.sendUpdate(sessionRef.getSessionId(), update); } finally { wsLock.unlock(); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java new file mode 100644 index 0000000000..776cdd40d2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/AuthCmd.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.service.ws; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AuthCmd implements WsCmd { + private int cmdId; + private String token; + + @Override + public WsCmdType getType() { + return WsCmdType.AUTH; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 0d7dc7fbfe..b1118224ef 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -66,8 +66,6 @@ import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionServi import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbTimeSeriesSubscription; import org.thingsboard.server.service.ws.notification.NotificationCommandsHandler; -import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; -import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd; import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd; @@ -124,7 +122,6 @@ public class DefaultWebSocketService implements WebSocketService { private static final String FAILED_TO_FETCH_DATA = "Failed to fetch data!"; private static final String FAILED_TO_FETCH_ATTRIBUTES = "Failed to fetch attributes!"; private static final String SESSION_META_DATA_NOT_FOUND = "Session meta-data not found!"; - private static final String FAILED_TO_PARSE_WS_COMMAND = "Failed to parse websocket command!"; private final ConcurrentMap wsSessionsMap = new ConcurrentHashMap<>(); @@ -192,7 +189,7 @@ public class DefaultWebSocketService implements WebSocketService { } @Override - public void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) { + public void handleSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) { String sessionId = sessionRef.getSessionId(); log.debug(PROCESSING_MSG, sessionId, event); switch (event.getEventType()) { @@ -212,46 +209,20 @@ public class DefaultWebSocketService implements WebSocketService { } @Override - public void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg) { - if (log.isTraceEnabled()) { - log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg); - } - - try { - WsCommandsWrapper cmdsWrapper; - switch (sessionRef.getSessionType()) { - case GENERAL: - cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); - break; - case TELEMETRY: - cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper(); - break; - case NOTIFICATIONS: - cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper(); - break; - default: - throw new IllegalArgumentException("Unknown session type"); - } - processCmds(sessionRef, cmdsWrapper); - } catch (Exception e) { - log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(UNKNOWN_SUBSCRIPTION_ID, SubscriptionErrorCode.BAD_REQUEST, FAILED_TO_PARSE_WS_COMMAND)); - } - } - - private void processCmds(WebSocketSessionRef sessionRef, WsCommandsWrapper cmdsWrapper) { - if (cmdsWrapper == null || CollectionUtils.isEmpty(cmdsWrapper.getCmds())) { + public void handleCommands(WebSocketSessionRef sessionRef, WsCommandsWrapper commandsWrapper) { + if (commandsWrapper == null || CollectionUtils.isEmpty(commandsWrapper.getCmds())) { return; } String sessionId = sessionRef.getSessionId(); - if (!validateSessionMetadata(sessionRef, cmdsWrapper.getCmds().get(0).getCmdId(), sessionId)) { + if (!validateSessionMetadata(sessionRef, UNKNOWN_SUBSCRIPTION_ID, sessionId)) { return; } - for (WsCmd cmd : cmdsWrapper.getCmds()) { + for (WsCmd cmd : commandsWrapper.getCmds()) { log.debug("[{}][{}][{}] Processing cmd: {}", sessionId, cmd.getType(), cmd.getCmdId(), cmd); try { - getCmdHandler(cmd.getType()).handle(sessionRef, cmd); + Optional.ofNullable(getCmdHandler(cmd.getType())) + .ifPresent(cmdHandler -> cmdHandler.handle(sessionRef, cmd)); } catch (Exception e) { log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e); @@ -288,19 +259,25 @@ public class DefaultWebSocketService implements WebSocketService { } @Override - public void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update) { - sendWsMsg(sessionId, update.getSubscriptionId(), update); + public void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update) { + sendUpdate(sessionId, update.getSubscriptionId(), update); } @Override - public void sendWsMsg(String sessionId, CmdUpdate update) { - sendWsMsg(sessionId, update.getCmdId(), update); + public void sendUpdate(String sessionId, CmdUpdate update) { + sendUpdate(sessionId, update.getCmdId(), update); } - private void sendWsMsg(String sessionId, int cmdId, T update) { + @Override + public void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg) { + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(subId, errorCode, errorMsg); + sendUpdate(sessionRef, update); + } + + private void sendUpdate(String sessionId, int cmdId, T update) { WsSessionMetaData md = wsSessionsMap.get(sessionId); if (md != null) { - sendWsMsg(md.getSessionRef(), cmdId, update); + sendUpdate(md.getSessionRef(), cmdId, update); } } @@ -472,7 +449,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendWsMsg(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), update); } finally { subLock.unlock(); } @@ -482,7 +459,7 @@ public class DefaultWebSocketService implements WebSocketService { subLock.lock(); try { oldSubService.addSubscription(sub); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); + sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); } finally { subLock.unlock(); } @@ -500,7 +477,7 @@ public class DefaultWebSocketService implements WebSocketService { update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_ATTRIBUTES); } - sendWsMsg(sessionRef, update); + sendUpdate(sessionRef, update); } }; @@ -529,7 +506,7 @@ public class DefaultWebSocketService implements WebSocketService { FutureCallback> callback = new FutureCallback>() { @Override public void onSuccess(List data) { - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); + sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); } @Override @@ -542,7 +519,7 @@ public class DefaultWebSocketService implements WebSocketService { update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_DATA); } - sendWsMsg(sessionRef, update); + sendUpdate(sessionRef, update); } }; accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, @@ -577,7 +554,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendWsMsg(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), update); } finally { subLock.unlock(); } @@ -588,7 +565,7 @@ public class DefaultWebSocketService implements WebSocketService { subLock.lock(); try { oldSubService.addSubscription(sub); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); + sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), attributesData)); } finally { subLock.unlock(); } @@ -672,7 +649,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendWsMsg(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), update); } finally { subLock.unlock(); } @@ -685,7 +662,7 @@ public class DefaultWebSocketService implements WebSocketService { subLock.lock(); try { oldSubService.addSubscription(sub); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); + sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); } finally { subLock.unlock(); } @@ -701,7 +678,7 @@ public class DefaultWebSocketService implements WebSocketService { update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, FAILED_TO_FETCH_DATA); } - sendWsMsg(sessionRef, update); + sendUpdate(sessionRef, update); } }; accessValidator.validate(sessionRef.getSecurityCtx(), Operation.READ_TELEMETRY, entityId, @@ -727,7 +704,7 @@ public class DefaultWebSocketService implements WebSocketService { .updateProcessor((subscription, update) -> { subLock.lock(); try { - sendWsMsg(subscription.getSessionId(), update); + sendUpdate(subscription.getSessionId(), update); } finally { subLock.unlock(); } @@ -740,7 +717,7 @@ public class DefaultWebSocketService implements WebSocketService { subLock.lock(); try { oldSubService.addSubscription(sub); - sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); + sendUpdate(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); } finally { subLock.unlock(); } @@ -829,20 +806,15 @@ public class DefaultWebSocketService implements WebSocketService { return true; } - private void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg) { - TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(subId, errorCode, errorMsg); - sendWsMsg(sessionRef, update); - } - - private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) { - sendWsMsg(sessionRef, update.getCmdId(), update); + private void sendUpdate(WebSocketSessionRef sessionRef, EntityDataUpdate update) { + sendUpdate(sessionRef, update.getCmdId(), update); } - private void sendWsMsg(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { - sendWsMsg(sessionRef, update.getSubscriptionId(), update); + private void sendUpdate(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) { + sendUpdate(sessionRef, update.getSubscriptionId(), update); } - private void sendWsMsg(WebSocketSessionRef sessionRef, int cmdId, Object update) { + private void sendUpdate(WebSocketSessionRef sessionRef, int cmdId, Object update) { try { String msg = JacksonUtil.OBJECT_MAPPER.writeValueAsString(update); executor.submit(() -> { @@ -997,7 +969,7 @@ public class DefaultWebSocketService implements WebSocketService { return cmdHandler; } } - throw new IllegalArgumentException("Unknown command type " + cmdType); + return null; } public static WsCmdHandler newCmdHandler(WsCmdType cmdType, BiConsumer handler) { diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java index 1ebd6101f0..19753b91ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.ws; import org.springframework.web.socket.CloseStatus; +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate; @@ -24,13 +25,15 @@ import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpda */ public interface WebSocketService { - void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent); + void handleSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent); - void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg); + void handleCommands(WebSocketSessionRef sessionRef, WsCommandsWrapper commandsWrapper); - void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update); + void sendUpdate(String sessionId, TelemetrySubscriptionUpdate update); - void sendWsMsg(String sessionId, CmdUpdate update); + void sendUpdate(String sessionId, CmdUpdate update); + + void sendError(WebSocketSessionRef sessionRef, int subId, SubscriptionErrorCode errorCode, String errorMsg); void close(String sessionId, CloseStatus status); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java index e799c8faa0..1dc75b0149 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java @@ -16,8 +16,7 @@ package org.thingsboard.server.service.ws; import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.Data; import org.thingsboard.server.service.security.model.SecurityUser; import java.net.InetSocketAddress; @@ -27,15 +26,14 @@ import java.util.concurrent.atomic.AtomicInteger; /** * Created by ashvayka on 27.03.18. */ -@RequiredArgsConstructor @Builder -@Getter +@Data public class WebSocketSessionRef { private static final long serialVersionUID = 1L; private final String sessionId; - private final SecurityUser securityCtx; + private SecurityUser securityCtx; private final InetSocketAddress localAddress; private final InetSocketAddress remoteAddress; private final WebSocketSessionType sessionType; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java index ef1d8520a2..285dd88efe 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCmdType.java @@ -16,6 +16,8 @@ package org.thingsboard.server.service.ws; public enum WsCmdType { + AUTH, + ATTRIBUTES, TIMESERIES, TIMESERIES_HISTORY, diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java index cbdbdf8c05..5704f3b2dc 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/WsCommandsWrapper.java @@ -45,6 +45,8 @@ import java.util.List; @NoArgsConstructor public class WsCommandsWrapper { + private AuthCmd authCmd; + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @Type(name = "ATTRIBUTES", value = AttributesSubscriptionCmd.class), diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 0937275a0d..5a02814870 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -245,7 +245,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void sendUpdate(String sessionId, CmdUpdate update) { log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update); - wsService.sendWsMsg(sessionId, update); + wsService.sendUpdate(sessionId, update); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 3e0eca1fab..8ba52a9b3c 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -42,7 +42,7 @@ public class NotificationCmdsWrapper { @JsonIgnore public WsCommandsWrapper toCommonCmdsWrapper() { - return new WsCommandsWrapper(Stream.of( + return new WsCommandsWrapper(null, Stream.of( unreadCountSubCmd, unreadSubCmd, markAsReadCmd, markAllAsReadCmd, unsubCmd ) .filter(Objects::nonNull) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java index e2029cbe36..1019124e6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryCmdsWrapper.java @@ -67,7 +67,7 @@ public class TelemetryCmdsWrapper { @JsonIgnore public WsCommandsWrapper toCommonCmdsWrapper() { - return new WsCommandsWrapper(Stream.of( + return new WsCommandsWrapper(null, Stream.of( attrSubCmds, tsSubCmds, historyCmds, entityDataCmds, entityDataUnsubscribeCmds, alarmDataCmds, alarmDataUnsubscribeCmds, entityCountCmds, entityCountUnsubscribeCmds, diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java new file mode 100644 index 0000000000..61ed4fc9ce --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.service.ws.telemetry.cmd.v2; + +import org.thingsboard.server.service.subscription.SubscriptionErrorCode; + +public class AuthCmdUpdate extends CmdUpdate { + + public AuthCmdUpdate(int cmdId) { + this(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null); + } + + public AuthCmdUpdate(int cmdId, int errorCode, String errorMsg) { + super(cmdId, errorCode, errorMsg); + } + + @Override + public CmdUpdateType getCmdUpdateType() { + return CmdUpdateType.AUTH; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java index f5b3809ce2..04b3cbd06e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java @@ -21,5 +21,6 @@ public enum CmdUpdateType { ALARM_COUNT_DATA, COUNT_DATA, NOTIFICATIONS, - NOTIFICATIONS_COUNT + NOTIFICATIONS_COUNT, + AUTH } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index fe09a187e1..1729285034 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -105,8 +105,11 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest { } protected TbTestWebSocketClient buildAndConnectWebSocketClient(String path) throws URISyntaxException, InterruptedException { - TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + path + "?token=" + token)); + TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + path)); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); + if (!path.contains("token=")) { + wsClient.authenticate(token); + } return wsClient; } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 0194911e5b..69eefa78bd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityFilter; import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.service.ws.AuthCmd; import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.WsCommandsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd; @@ -64,6 +65,13 @@ public class TbTestWebSocketClient extends WebSocketClient { } + public void authenticate(String token) { + WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); + cmdsWrapper.setAuthCmd(new AuthCmd(1, token)); + send(JacksonUtil.toString(cmdsWrapper)); + waitForReply(); + } + @Override public void onMessage(String s) { log.info("RECEIVED: {}", s); diff --git a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java index 070fda9474..47ce021dae 100644 --- a/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java @@ -667,7 +667,7 @@ public class WebsocketApiTest extends AbstractControllerTest { ObjectNode wrapperNode = JacksonUtil.newObjectNode(); wrapperNode.set("entityCountCmds", entityCountCmds); - wsClient = buildAndConnectWebSocketClient("/api/ws/plugins/telemetry"); + wsClient = buildAndConnectWebSocketClient("/api/ws/plugins/telemetry?token=" + token); wsClient.send(JacksonUtil.toString(wrapperNode)); EntityCountUpdate update = wsClient.parseCountReply(wsClient.waitForReply()); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index 27a0eed1a4..f7fc195146 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -259,8 +259,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest @Override protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { - NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token); + NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort); assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); + wsClient.authenticate(token); return wsClient; } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 2cff2939c2..d96d04b31a 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -48,8 +48,8 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { private int unreadCount; private List notifications; - public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { - super(new URI(wsUrl + "/api/ws?token=" + token)); + public NotificationApiWsClient(String wsUrl) throws URISyntaxException { + super(new URI(wsUrl + "/api/ws")); } public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java index 7bb4f1e559..48070678f6 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java @@ -106,7 +106,7 @@ public class JwtTokenFactoryTest { AccessJwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); checkExpirationTime(accessToken, jwtSettings.getTokenExpirationTime()); - SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(new RawAccessJwtToken(accessToken.getToken())); + SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(accessToken.getToken()); assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId()); assertThat(parsedSecurityUser.getEmail()).isEqualTo(securityUser.getEmail()); assertThat(parsedSecurityUser.getUserPrincipal()).matches(userPrincipal -> { @@ -135,7 +135,7 @@ public class JwtTokenFactoryTest { JwtToken refreshToken = tokenFactory.createRefreshToken(securityUser); checkExpirationTime(refreshToken, jwtSettings.getRefreshTokenExpTime()); - SecurityUser parsedSecurityUser = tokenFactory.parseRefreshToken(new RawAccessJwtToken(refreshToken.getToken())); + SecurityUser parsedSecurityUser = tokenFactory.parseRefreshToken(refreshToken.getToken()); assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId()); assertThat(parsedSecurityUser.getUserPrincipal()).matches(userPrincipal -> { return userPrincipal.getType().equals(securityUser.getUserPrincipal().getType()) @@ -159,7 +159,7 @@ public class JwtTokenFactoryTest { JwtToken preVerificationToken = tokenFactory.createPreVerificationToken(securityUser, tokenLifetime); checkExpirationTime(preVerificationToken, tokenLifetime); - SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(new RawAccessJwtToken(preVerificationToken.getToken())); + SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(preVerificationToken.getToken()); assertThat(parsedSecurityUser.getId()).isEqualTo(securityUser.getId()); assertThat(parsedSecurityUser.getAuthority()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN); assertThat(parsedSecurityUser.getTenantId()).isEqualTo(securityUser.getTenantId()); @@ -198,7 +198,7 @@ public class JwtTokenFactoryTest { } private void checkExpirationTime(JwtToken jwtToken, int tokenLifetime) { - Claims claims = tokenFactory.parseTokenClaims(jwtToken).getBody(); + Claims claims = tokenFactory.parseTokenClaims(jwtToken.getToken()).getBody(); assertThat(claims.getExpiration()).matches(actualExpirationTime -> { Calendar expirationTime = Calendar.getInstance(); expirationTime.setTime(new Date()); diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java index eeb9fe64a2..90d911833c 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java @@ -114,12 +114,12 @@ public class TokenOutdatingTest { // Token outdatage time is rounded to 1 sec. Need to wait before outdating so that outdatage time is strictly after token issue time SECONDS.sleep(1); eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); - assertTrue(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId())); + assertTrue(tokenOutdatingService.isOutdated(jwtToken.getToken(), securityUser.getId())); SECONDS.sleep(1); JwtToken newJwtToken = tokenFactory.createAccessJwtToken(securityUser); - assertFalse(tokenOutdatingService.isOutdated(newJwtToken, securityUser.getId())); + assertFalse(tokenOutdatingService.isOutdated(newJwtToken.getToken(), securityUser.getId())); } @Test From 07dd8f65e5240c43f97797c512593399764921ba Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 8 Dec 2023 18:05:47 +0200 Subject: [PATCH 09/14] UI: Added auth msg in WS --- ui-ngx/src/app/core/ws/websocket.service.ts | 24 ++++++++++++++----- .../models/telemetry/telemetry.models.ts | 16 +++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/core/ws/websocket.service.ts b/ui-ngx/src/app/core/ws/websocket.service.ts index a690ba08b6..02f70433c2 100644 --- a/ui-ngx/src/app/core/ws/websocket.service.ts +++ b/ui-ngx/src/app/core/ws/websocket.service.ts @@ -21,10 +21,16 @@ import { AuthService } from '@core/auth/auth.service'; import { NgZone } from '@angular/core'; import { selectIsAuthenticated } from '@core/auth/auth.selectors'; import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; -import { CmdUpdateMsg, TelemetrySubscriber, WebsocketDataMsg } from '@shared/models/telemetry/telemetry.models'; +import { + AuthCmd, + AuthWsCmd, + CmdUpdateMsg, + TelemetrySubscriber, + WebsocketDataMsg +} from '@shared/models/telemetry/telemetry.models'; import { ActionNotificationShow } from '@core/notification/notification.actions'; -import Timeout = NodeJS.Timeout; import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models'; +import Timeout = NodeJS.Timeout; const RECONNECT_INTERVAL = 2000; const WS_IDLE_TIMEOUT = 90000; @@ -48,7 +54,7 @@ export abstract class WebsocketService implements WsServ wsUri: string; - dataStream: WebSocketSubject; + dataStream: WebSocketSubject; errorName = 'WebSocket Error'; @@ -158,13 +164,13 @@ export abstract class WebsocketService implements WsServ } private openSocket(token: string) { - const uri = `${this.wsUri}?token=${token}`; + const uri = `${this.wsUri}`; this.dataStream = webSocket( { url: uri, openObserver: { next: () => { - this.onOpen(); + this.onOpen(token); } }, closeObserver: { @@ -187,9 +193,10 @@ export abstract class WebsocketService implements WsServ }); } - private onOpen() { + private onOpen(token: string) { this.isOpening = false; this.isOpened = true; + this.dataStream.next(this.createdAuthMsg(token)); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; @@ -260,4 +267,9 @@ export abstract class WebsocketService implements WsServ })); } + private createdAuthMsg(token: string): AuthWsCmd { + return { + authCmd: new AuthCmd(token) + }; + } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 671b503c92..9c2b3f2b48 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -122,6 +122,8 @@ export enum DataSortOrder { } export enum WsCmdType { + AUTH = 'AUTH', + ATTRIBUTES = 'ATTRIBUTES', TIMESERIES = 'TIMESERIES', TIMESERIES_HISTORY = 'TIMESERIES_HISTORY', @@ -147,6 +149,10 @@ export interface WebsocketCmd { type: WsCmdType; } +export interface AuthWsCmd { + authCmd: AuthCmd; +} + export interface TelemetryPluginCmd extends WebsocketCmd { keys: string; } @@ -289,6 +295,16 @@ export class AlarmCountUnsubscribeCmd implements WebsocketCmd { type = WsCmdType.ALARM_COUNT_UNSUBSCRIBE; } +export class AuthCmd implements WebsocketCmd { + cmdId = 0; + type: WsCmdType.AUTH; + token: string; + + constructor(token: string) { + this.token = token; + } +} + export class TelemetryPluginCmdsWrapper implements CmdWrapper { constructor() { From fb92aef8cb24cb3ba067ecc4d1431fd6d147d26e Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 11 Dec 2023 11:24:55 +0200 Subject: [PATCH 10/14] WS inbound msg queue --- .../controller/plugin/TbWebSocketHandler.java | 178 ++++++++++-------- .../ws/telemetry/cmd/v2/AuthCmdUpdate.java | 34 ---- .../ws/telemetry/cmd/v2/CmdUpdateType.java | 3 +- .../controller/TbTestWebSocketClient.java | 1 - .../plugin/TbWebSocketHandlerTest.java | 29 ++- 5 files changed, 132 insertions(+), 113 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 855cbbc1e5..0cdb82b612 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -19,6 +19,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.BeanCreationNotAllowedException; @@ -57,7 +58,6 @@ import org.thingsboard.server.service.ws.WebSocketSessionType; import org.thingsboard.server.service.ws.WsCommandsWrapper; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper; -import org.thingsboard.server.service.ws.telemetry.cmd.v2.AuthCmdUpdate; import javax.websocket.RemoteEndpoint; import javax.websocket.SendHandler; @@ -70,10 +70,13 @@ import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @@ -131,58 +134,60 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!")); return; } - WebSocketSessionRef sessionRef = sessionMd.sessionRef; String msg = message.getPayload(); + sessionMd.onMsg(msg); + } catch (IOException e) { + log.warn("IO error", e); + } + } - WsCommandsWrapper cmdsWrapper; - try { - switch (sessionRef.getSessionType()) { - case GENERAL: - cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); - break; - case TELEMETRY: - cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper(); - break; - case NOTIFICATIONS: - cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper(); - break; - default: - return; - } - } catch (Exception e) { - log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); - if (sessionRef.getSecurityCtx() != null) { - webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload"); - } else { - close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); - } - return; + void processMsg(SessionMetaData sessionMd, String msg) throws IOException { + WebSocketSessionRef sessionRef = sessionMd.sessionRef; + WsCommandsWrapper cmdsWrapper; + try { + switch (sessionRef.getSessionType()) { + case GENERAL: + cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class); + break; + case TELEMETRY: + cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper(); + break; + case NOTIFICATIONS: + cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper(); + break; + default: + return; } - + } catch (Exception e) { + log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); if (sessionRef.getSecurityCtx() != null) { - log.trace("[{}][{}] Processing {}", sessionRef.getSecurityCtx().getTenantId(), session.getId(), msg); - webSocketService.handleCommands(sessionRef, cmdsWrapper); + webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload"); } else { - AuthCmd authCmd = cmdsWrapper.getAuthCmd(); - if (authCmd == null) { - close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing")); - return; - } - log.trace("[{}] Authenticating session", session.getId()); - SecurityUser securityCtx; - try { - securityCtx = authenticationProvider.authenticate(authCmd.getToken()); - } catch (Exception e) { - close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); - return; - } - sessionRef.setSecurityCtx(securityCtx); - pendingSessions.invalidate(session.getId()); - establishSession(session, sessionRef); - webSocketService.sendUpdate(sessionRef.getSessionId(), new AuthCmdUpdate(1)); + close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); } - } catch (IOException e) { - log.warn("IO error", e); + return; + } + + if (sessionRef.getSecurityCtx() != null) { + log.trace("[{}][{}] Processing {}", sessionRef.getSecurityCtx().getTenantId(), sessionMd.session.getId(), msg); + webSocketService.handleCommands(sessionRef, cmdsWrapper); + } else { + AuthCmd authCmd = cmdsWrapper.getAuthCmd(); + if (authCmd == null) { + close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing")); + return; + } + log.trace("[{}] Authenticating session", sessionMd.session.getId()); + SecurityUser securityCtx; + try { + securityCtx = authenticationProvider.authenticate(authCmd.getToken()); + } catch (Exception e) { + close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage())); + return; + } + sessionRef.setSecurityCtx(securityCtx); + pendingSessions.invalidate(sessionMd.session.getId()); + establishSession(sessionMd.session, sessionRef, sessionMd); } } @@ -214,7 +219,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } WebSocketSessionRef sessionRef = toRef(session); log.debug("[{}][{}] Session opened from address: {}", sessionRef.getSessionId(), session.getId(), session.getRemoteAddress()); - establishSession(session, sessionRef); + establishSession(session, sessionRef, null); } catch (InvalidParameterException e) { log.warn("[{}] Failed to start session", session.getId(), e); session.close(CloseStatus.BAD_DATA.withReason(e.getMessage())); @@ -224,24 +229,26 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - private void establishSession(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException { + private void establishSession(WebSocketSession session, WebSocketSessionRef sessionRef, SessionMetaData sessionMd) throws IOException { if (sessionRef.getSecurityCtx() != null) { if (!checkLimits(session, sessionRef)) { return; } - var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); - int wsTenantProfileQueueLimit = tenantProfileConfiguration != null ? - tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : wsMaxQueueMessagesPerSession; - SessionMetaData sessionMd = new SessionMetaData(session, sessionRef, - (wsTenantProfileQueueLimit > 0 && wsTenantProfileQueueLimit < wsMaxQueueMessagesPerSession) ? - wsTenantProfileQueueLimit : wsMaxQueueMessagesPerSession); + int maxMsgQueueSize = Optional.ofNullable(getTenantProfileConfiguration(sessionRef)) + .map(DefaultTenantProfileConfiguration::getWsMsgQueueLimitPerSession) + .filter(profileLimit -> profileLimit > 0 && profileLimit < wsMaxQueueMessagesPerSession) + .orElse(wsMaxQueueMessagesPerSession); + if (sessionMd == null) { + sessionMd = new SessionMetaData(session, sessionRef); + } + sessionMd.setMaxMsgQueueSize(maxMsgQueueSize); internalSessionMap.put(session.getId(), sessionMd); externalSessionMap.put(sessionRef.getSessionId(), session.getId()); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); log.info("[{}][{}][{}] Session established from address: {}", sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSessionId(), session.getId(), session.getRemoteAddress()); } else { - SessionMetaData sessionMd = new SessionMetaData(session, sessionRef, wsMaxQueueMessagesPerSession); + sessionMd = new SessionMetaData(session, sessionRef); pendingSessions.put(session.getId(), sessionMd); externalSessionMap.put(sessionRef.getSessionId(), session.getId()); } @@ -328,19 +335,22 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private final WebSocketSessionRef sessionRef; final AtomicBoolean isSending = new AtomicBoolean(false); - private final Queue> msgQueue; + private final Queue> outboundMsgQueue = new ConcurrentLinkedQueue<>(); + private final AtomicInteger outboundMsgQueueSize = new AtomicInteger(); + @Setter + private int maxMsgQueueSize = wsMaxQueueMessagesPerSession; - // TODO: msg queue as in org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx + private final Queue inboundMsgQueue = new ConcurrentLinkedQueue<>(); + private final Lock inboundMsgQueueProcessorLock = new ReentrantLock(); private volatile long lastActivityTime; - SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { + SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef) { super(); this.session = session; Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class); this.asyncRemote = nativeSession.getAsyncRemote(); this.sessionRef = sessionRef; - this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession); this.lastActivityTime = System.currentTimeMillis(); } @@ -365,7 +375,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } catch (IOException ioe) { log.trace("[{}] Session transport error", session.getId(), ioe); } finally { - msgQueue.clear(); + outboundMsgQueue.clear(); } } @@ -378,19 +388,14 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } void sendMsg(TbWebSocketMsg msg) { - try { - msgQueue.add(msg); - } catch (RuntimeException e) { - if (log.isTraceEnabled()) { - log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); - } else { - log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); - } + if (outboundMsgQueueSize.get() < maxMsgQueueSize) { + outboundMsgQueue.add(msg); + outboundMsgQueueSize.incrementAndGet(); + processNextMsg(); + } else { + log.info("[{}][{}] Session closed due to updates queue size exceeded", sessionRef.getSecurityCtx().getTenantId(), session.getId()); closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); - return; } - - processNextMsg(); } private void sendMsgInternal(TbWebSocketMsg msg) { @@ -424,16 +429,39 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } private void processNextMsg() { - if (msgQueue.isEmpty() || !isSending.compareAndSet(false, true)) { + if (outboundMsgQueue.isEmpty() || !isSending.compareAndSet(false, true)) { return; } - TbWebSocketMsg msg = msgQueue.poll(); + TbWebSocketMsg msg = outboundMsgQueue.poll(); if (msg != null) { + outboundMsgQueueSize.decrementAndGet(); sendMsgInternal(msg); } else { isSending.set(false); } } + + public void onMsg(String msg) throws IOException { + inboundMsgQueue.add(msg); + tryProcessInboundMsgs(); + } + + void tryProcessInboundMsgs() throws IOException { + while (!inboundMsgQueue.isEmpty()) { + if (inboundMsgQueueProcessorLock.tryLock()) { + try { + String msg; + while ((msg = inboundMsgQueue.poll()) != null) { + processMsg(this, msg); + } + } finally { + inboundMsgQueueProcessorLock.unlock(); + } + } else { + return; + } + } + } } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java deleted file mode 100644 index 61ed4fc9ce..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/AuthCmdUpdate.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright © 2016-2023 The Thingsboard Authors - * - * Licensed 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.thingsboard.server.service.ws.telemetry.cmd.v2; - -import org.thingsboard.server.service.subscription.SubscriptionErrorCode; - -public class AuthCmdUpdate extends CmdUpdate { - - public AuthCmdUpdate(int cmdId) { - this(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null); - } - - public AuthCmdUpdate(int cmdId, int errorCode, String errorMsg) { - super(cmdId, errorCode, errorMsg); - } - - @Override - public CmdUpdateType getCmdUpdateType() { - return CmdUpdateType.AUTH; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java index 04b3cbd06e..f5b3809ce2 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v2/CmdUpdateType.java @@ -21,6 +21,5 @@ public enum CmdUpdateType { ALARM_COUNT_DATA, COUNT_DATA, NOTIFICATIONS, - NOTIFICATIONS_COUNT, - AUTH + NOTIFICATIONS_COUNT } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 69eefa78bd..f7082782a3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -69,7 +69,6 @@ public class TbTestWebSocketClient extends WebSocketClient { WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper(); cmdsWrapper.setAuthCmd(new AuthCmd(1, token)); send(JacksonUtil.toString(cmdsWrapper)); - waitForReply(); } @Override diff --git a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java index 0394e8a505..7d3d68f2d3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/plugin/TbWebSocketHandlerTest.java @@ -32,7 +32,10 @@ import javax.websocket.SendResult; import javax.websocket.Session; import java.io.IOException; import java.util.Collection; +import java.util.Deque; import java.util.List; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -49,6 +52,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -79,7 +83,9 @@ class TbWebSocketHandlerTest { asyncRemote = mock(RemoteEndpoint.Async.class); willReturn(asyncRemote).given(nativeSession).getAsyncRemote(); sessionRef = mock(WebSocketSessionRef.class, Mockito.RETURNS_DEEP_STUBS); //prevent NPE on logs - sendHandler = spy(wsHandler.new SessionMetaData(session, sessionRef, maxMsgQueuePerSession)); + TbWebSocketHandler.SessionMetaData sessionMd = wsHandler.new SessionMetaData(session, sessionRef); + sessionMd.setMaxMsgQueueSize(maxMsgQueuePerSession); + sendHandler = spy(sessionMd); } @AfterEach @@ -157,4 +163,25 @@ class TbWebSocketHandlerTest { verify(asyncRemote, times(1)).sendText(anyString(), any()); } + @Test + void sendHandler_onMsg_allProcessed() throws Exception { + Deque msgs = new ConcurrentLinkedDeque<>(); + doAnswer(inv -> msgs.add(inv.getArgument(1))).when(wsHandler).processMsg(any(), any()); + for (int i = 0; i < 100; i++) { + String msg = String.valueOf(i); + executor.submit(() -> { + try { + Thread.sleep(new Random().nextInt(50)); + sendHandler.onMsg(msg); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + + assertThat(msgs).map(Integer::parseInt).doesNotHaveDuplicates().hasSize(100); + } + } From e52782ee0e5d25b8179a61e311146597399f06ed Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 6 Dec 2023 17:21:44 +0200 Subject: [PATCH 11/14] Add sequenceNumber to notification subscription updates --- .../server/service/subscription/TbSubscription.java | 3 +++ .../ws/notification/cmd/UnreadNotificationsCountUpdate.java | 5 ++++- .../ws/notification/cmd/UnreadNotificationsUpdate.java | 5 ++++- .../ws/notification/sub/NotificationsCountSubscription.java | 1 + .../ws/notification/sub/NotificationsSubscription.java | 3 +++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 1cda590a1f..04007e949c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @Data @@ -35,6 +36,8 @@ public abstract class TbSubscription { private final TbSubscriptionType type; private final BiConsumer, T> updateProcessor; + protected final AtomicInteger sequence = new AtomicInteger(); + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java index 93f51a965b..42dc670a21 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java @@ -28,14 +28,17 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; public class UnreadNotificationsCountUpdate extends CmdUpdate { private final int totalUnreadCount; + private final int sequenceNumber; @Builder @JsonCreator public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, @JsonProperty("errorMsg") String errorMsg, - @JsonProperty("totalUnreadCount") int totalUnreadCount) { + @JsonProperty("totalUnreadCount") int totalUnreadCount, + @JsonProperty("sequenceNumber") int sequenceNumber) { super(cmdId, errorCode, errorMsg); this.totalUnreadCount = totalUnreadCount; + this.sequenceNumber = sequenceNumber; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java index e64624d16e..65492f9579 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -33,6 +33,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate { private final Collection notifications; private final Notification update; private final int totalUnreadCount; + private final int sequenceNumber; @Builder @JsonCreator @@ -40,11 +41,13 @@ public class UnreadNotificationsUpdate extends CmdUpdate { @JsonProperty("errorMsg") String errorMsg, @JsonProperty("notifications") Collection notifications, @JsonProperty("update") Notification update, - @JsonProperty("totalUnreadCount") int totalUnreadCount) { + @JsonProperty("totalUnreadCount") int totalUnreadCount, + @JsonProperty("sequenceNumber") int sequenceNumber) { super(cmdId, errorCode, errorMsg); this.notifications = notifications; this.update = update; this.totalUnreadCount = totalUnreadCount; + this.sequenceNumber = sequenceNumber; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java index 5263f9d651..94939682e9 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java @@ -41,6 +41,7 @@ public class NotificationsCountSubscription extends TbSubscription Date: Mon, 11 Dec 2023 12:43:41 +0200 Subject: [PATCH 12/14] UI: Added sequenceNumber in WS notification updated --- .../notification-bell.component.ts | 6 +++-- .../show-notification-popover.component.ts | 6 +++-- .../models/telemetry/telemetry.models.ts | 4 +++ .../websocket/notification-ws.models.ts | 25 ++++++++++++++++--- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts index 7c68b2df65..4399ce5d42 100644 --- a/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts +++ b/ui-ngx/src/app/modules/home/components/notification/notification-bell.component.ts @@ -25,7 +25,7 @@ import { } from '@angular/core'; import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; import { BehaviorSubject, ReplaySubject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map, share, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, share, skip, tap } from 'rxjs/operators'; import { MatButton } from '@angular/material/button'; import { TbPopoverService } from '@shared/components/popover.service'; import { ShowNotificationPopoverComponent } from '@home/components/notification/show-notification-popover.component'; @@ -100,7 +100,9 @@ export class NotificationBellComponent implements OnDestroy { private initSubscription() { this.notificationSubscriber = NotificationSubscriber.createNotificationCountSubscription(this.notificationWsService, this.zone); - this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.countSubject.next(value)); + this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.pipe( + skip(1), + ).subscribe(value => this.countSubject.next(value)); this.notificationSubscriber.subscribe(); } diff --git a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts index b2a9009a57..6771d3fdda 100644 --- a/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts +++ b/ui-ngx/src/app/modules/home/components/notification/show-notification-popover.component.ts @@ -22,7 +22,7 @@ import { AppState } from '@core/core.state'; import { Notification, NotificationRequest } from '@shared/models/notification.models'; import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; import { BehaviorSubject, Observable, ReplaySubject, Subscription } from 'rxjs'; -import { map, share, tap } from 'rxjs/operators'; +import { map, share, skip, tap } from 'rxjs/operators'; import { Router } from '@angular/router'; import { NotificationSubscriber } from '@shared/models/websocket/notification-ws.models'; @@ -71,7 +71,9 @@ export class ShowNotificationPopoverComponent extends PageComponent implements O }), tap(() => setTimeout(() => this.cd.markForCheck())) ); - this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.subscribe(value => this.counter.next(value)); + this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.pipe( + skip(1), + ).subscribe(value => this.counter.next(value)); this.notificationSubscriber.subscribe(); } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 9c2b3f2b48..de237b35c2 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -610,21 +610,25 @@ export class AlarmCountUpdate extends CmdUpdate { export class NotificationCountUpdate extends CmdUpdate { totalUnreadCount: number; + sequenceNumber: number; constructor(msg: NotificationCountUpdateMsg) { super(msg); this.totalUnreadCount = msg.totalUnreadCount; + this.sequenceNumber = msg.sequenceNumber; } } export class NotificationsUpdate extends CmdUpdate { totalUnreadCount: number; + sequenceNumber: number; update?: Notification; notifications?: Notification[]; constructor(msg: NotificationsUpdateMsg) { super(msg); this.totalUnreadCount = msg.totalUnreadCount; + this.sequenceNumber = msg.sequenceNumber; this.update = msg.update; this.notifications = msg.notifications; } diff --git a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts index ea85a7ffcc..a4e4a01713 100644 --- a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts +++ b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts @@ -27,19 +27,27 @@ import { NgZone } from '@angular/core'; import { isDefinedAndNotNull } from '@core/utils'; import { Notification } from '@shared/models/notification.models'; import { WsService, WsSubscriber } from '@shared/models/websocket/websocket.models'; -import { BehaviorSubject, ReplaySubject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { WebsocketService } from '@core/ws/websocket.service'; export class NotificationSubscriber extends WsSubscriber { - private notificationCountSubject = new ReplaySubject(1); + private notificationCountSubject = new BehaviorSubject({ + cmdId: 0, + cmdUpdateType: undefined, + errorCode: 0, + errorMsg: '', + totalUnreadCount: 0, + sequenceNumber: 0 + }); private notificationsSubject = new BehaviorSubject({ cmdId: 0, cmdUpdateType: undefined, errorCode: 0, errorMsg: '', notifications: null, - totalUnreadCount: 0 + totalUnreadCount: 0, + sequenceNumber: 0 }); public messageLimit = 10; @@ -84,6 +92,10 @@ export class NotificationSubscriber extends WsSubscriber { } onNotificationCountUpdate(message: NotificationCountUpdate) { + const currentNotificationCount = this.notificationCountSubject.value; + if (message.sequenceNumber <= currentNotificationCount.sequenceNumber) { + return; + } if (this.zone) { this.zone.run( () => { @@ -103,6 +115,9 @@ export class NotificationSubscriber extends WsSubscriber { onNotificationsUpdate(message: NotificationsUpdate) { const currentNotifications = this.notificationsSubject.value; + if (message.sequenceNumber <= currentNotifications.sequenceNumber) { + message.totalUnreadCount = currentNotifications.totalUnreadCount; + } let processMessage = message; if (isDefinedAndNotNull(currentNotifications) && message.update) { currentNotifications.notifications.unshift(message.update); @@ -165,13 +180,15 @@ export class MarkAllAsReadCmd implements WebsocketCmd { export interface NotificationCountUpdateMsg extends CmdUpdateMsg { cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT; totalUnreadCount: number; + sequenceNumber: number; } export interface NotificationsUpdateMsg extends CmdUpdateMsg { cmdUpdateType: CmdUpdateType.NOTIFICATIONS; - totalUnreadCount: number; update?: Notification; notifications?: Notification[]; + totalUnreadCount: number; + sequenceNumber: number; } export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => { From 1d674046d3165af20b1c0ce5b0f305b64a72d291 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 11 Dec 2023 13:30:34 +0200 Subject: [PATCH 13/14] UI: Refactoring WS after review --- .../core/ws/telemetry-websocket.service.ts | 30 ++- ui-ngx/src/app/core/ws/websocket.service.ts | 2 +- .../notification-bell.component.ts | 2 +- .../show-notification-popover.component.ts | 2 +- ui-ngx/src/app/shared/models/public-api.ts | 1 - .../models/telemetry/telemetry.models.ts | 178 ++++++++++++++- .../websocket/notification-ws.models.ts | 202 ------------------ 7 files changed, 191 insertions(+), 226 deletions(-) delete mode 100644 ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index d89bcc2b2e..f8fe055136 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -32,12 +32,20 @@ import { isAlarmDataUpdateMsg, isEntityCountUpdateMsg, isEntityDataUpdateMsg, + isNotificationCountUpdateMsg, + isNotificationsUpdateMsg, + MarkAllAsReadCmd, + MarkAsReadCmd, NotificationCountUpdate, + NotificationSubscriber, NotificationsUpdate, SubscriptionCmd, SubscriptionUpdate, TelemetryPluginCmdsWrapper, TelemetrySubscriber, + UnreadCountSubCmd, + UnreadSubCmd, + UnsubscribeCmd, WebsocketDataMsg } from '@app/shared/models/telemetry/telemetry.models'; import { Store } from '@ngrx/store'; @@ -45,16 +53,6 @@ import { AppState } from '@core/core.state'; import { AuthService } from '@core/auth/auth.service'; import { WINDOW } from '@core/services/window.service'; import { WebsocketService } from '@core/ws/websocket.service'; -import { - isNotificationCountUpdateMsg, - isNotificationsUpdateMsg, - MarkAllAsReadCmd, - MarkAsReadCmd, - NotificationSubscriber, - UnreadCountSubCmd, - UnreadSubCmd, - UnsubscribeCmd -} from '@shared/models/websocket/notification-ws.models'; // @dynamic @Injectable({ @@ -145,19 +143,19 @@ export class TelemetryWebsocketService extends WebsocketService { + const updateMsg = (message as CmdUpdateMsg); + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT; +}; + +export const isNotificationsUpdateMsg = (message: WebsocketDataMsg): message is NotificationsUpdateMsg => { + const updateMsg = (message as CmdUpdateMsg); + return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS; +}; + export class SubscriptionUpdate implements SubscriptionUpdateMsg { subscriptionId: number; errorCode: number; @@ -796,3 +856,113 @@ export class TelemetrySubscriber extends WsSubscriber { ); } } + +export class NotificationSubscriber extends WsSubscriber { + private notificationCountSubject = new BehaviorSubject({ + cmdId: 0, + cmdUpdateType: undefined, + errorCode: 0, + errorMsg: '', + totalUnreadCount: 0, + sequenceNumber: 0 + }); + private notificationsSubject = new BehaviorSubject({ + cmdId: 0, + cmdUpdateType: undefined, + errorCode: 0, + errorMsg: '', + notifications: null, + totalUnreadCount: 0, + sequenceNumber: 0 + }); + + public messageLimit = 10; + + public notificationCount$ = this.notificationCountSubject.asObservable().pipe(map(msg => msg.totalUnreadCount)); + public notifications$ = this.notificationsSubject.asObservable().pipe(map(msg => msg.notifications )); + + public static createNotificationCountSubscription(websocketService: WebsocketService, + zone: NgZone): NotificationSubscriber { + const subscriptionCommand = new UnreadCountSubCmd(); + const subscriber = new NotificationSubscriber(websocketService, zone); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createNotificationsSubscription(websocketService: WebsocketService, + zone: NgZone, limit = 10): NotificationSubscriber { + const subscriptionCommand = new UnreadSubCmd(limit); + const subscriber = new NotificationSubscriber(websocketService, zone); + subscriber.messageLimit = limit; + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createMarkAsReadCommand(websocketService: WebsocketService, + ids: string[]): NotificationSubscriber { + const subscriptionCommand = new MarkAsReadCmd(ids); + const subscriber = new NotificationSubscriber(websocketService); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + public static createMarkAllAsReadCommand(websocketService: WebsocketService): NotificationSubscriber { + const subscriptionCommand = new MarkAllAsReadCmd(); + const subscriber = new NotificationSubscriber(websocketService); + subscriber.subscriptionCommands.push(subscriptionCommand); + return subscriber; + } + + constructor(private websocketService: WsService, protected zone?: NgZone) { + super(websocketService, zone); + } + + onNotificationCountUpdate(message: NotificationCountUpdate) { + const currentNotificationCount = this.notificationCountSubject.value; + if (message.sequenceNumber <= currentNotificationCount.sequenceNumber) { + return; + } + if (this.zone) { + this.zone.run( + () => { + this.notificationCountSubject.next(message); + } + ); + } else { + this.notificationCountSubject.next(message); + } + } + + public complete() { + this.notificationCountSubject.complete(); + this.notificationsSubject.complete(); + super.complete(); + } + + onNotificationsUpdate(message: NotificationsUpdate) { + const currentNotifications = this.notificationsSubject.value; + if (message.sequenceNumber <= currentNotifications.sequenceNumber) { + message.totalUnreadCount = currentNotifications.totalUnreadCount; + } + let processMessage = message; + if (isDefinedAndNotNull(currentNotifications) && message.update) { + currentNotifications.notifications.unshift(message.update); + if (currentNotifications.notifications.length > this.messageLimit) { + currentNotifications.notifications.pop(); + } + processMessage = currentNotifications; + processMessage.totalUnreadCount = message.totalUnreadCount; + } + if (this.zone) { + this.zone.run( + () => { + this.notificationsSubject.next(processMessage); + this.notificationCountSubject.next(processMessage); + } + ); + } else { + this.notificationsSubject.next(processMessage); + this.notificationCountSubject.next(processMessage); + } + } +} diff --git a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts b/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts deleted file mode 100644 index a4e4a01713..0000000000 --- a/ui-ngx/src/app/shared/models/websocket/notification-ws.models.ts +++ /dev/null @@ -1,202 +0,0 @@ -/// -/// Copyright © 2016-2023 The Thingsboard Authors -/// -/// Licensed 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. -/// - -import { - CmdUpdateMsg, - CmdUpdateType, - NotificationCountUpdate, - NotificationsUpdate, - WebsocketCmd, - WebsocketDataMsg, - WsCmdType -} from '@shared/models/telemetry/telemetry.models'; -import { NgZone } from '@angular/core'; -import { isDefinedAndNotNull } from '@core/utils'; -import { Notification } from '@shared/models/notification.models'; -import { WsService, WsSubscriber } from '@shared/models/websocket/websocket.models'; -import { BehaviorSubject } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { WebsocketService } from '@core/ws/websocket.service'; - -export class NotificationSubscriber extends WsSubscriber { - private notificationCountSubject = new BehaviorSubject({ - cmdId: 0, - cmdUpdateType: undefined, - errorCode: 0, - errorMsg: '', - totalUnreadCount: 0, - sequenceNumber: 0 - }); - private notificationsSubject = new BehaviorSubject({ - cmdId: 0, - cmdUpdateType: undefined, - errorCode: 0, - errorMsg: '', - notifications: null, - totalUnreadCount: 0, - sequenceNumber: 0 - }); - - public messageLimit = 10; - - public notificationCount$ = this.notificationCountSubject.asObservable().pipe(map(msg => msg.totalUnreadCount)); - public notifications$ = this.notificationsSubject.asObservable().pipe(map(msg => msg.notifications )); - - public static createNotificationCountSubscription(websocketService: WebsocketService, - zone: NgZone): NotificationSubscriber { - const subscriptionCommand = new UnreadCountSubCmd(); - const subscriber = new NotificationSubscriber(websocketService, zone); - subscriber.subscriptionCommands.push(subscriptionCommand); - return subscriber; - } - - public static createNotificationsSubscription(websocketService: WebsocketService, - zone: NgZone, limit = 10): NotificationSubscriber { - const subscriptionCommand = new UnreadSubCmd(limit); - const subscriber = new NotificationSubscriber(websocketService, zone); - subscriber.messageLimit = limit; - subscriber.subscriptionCommands.push(subscriptionCommand); - return subscriber; - } - - public static createMarkAsReadCommand(websocketService: WebsocketService, - ids: string[]): NotificationSubscriber { - const subscriptionCommand = new MarkAsReadCmd(ids); - const subscriber = new NotificationSubscriber(websocketService); - subscriber.subscriptionCommands.push(subscriptionCommand); - return subscriber; - } - - public static createMarkAllAsReadCommand(websocketService: WebsocketService): NotificationSubscriber { - const subscriptionCommand = new MarkAllAsReadCmd(); - const subscriber = new NotificationSubscriber(websocketService); - subscriber.subscriptionCommands.push(subscriptionCommand); - return subscriber; - } - - constructor(private websocketService: WsService, protected zone?: NgZone) { - super(websocketService, zone); - } - - onNotificationCountUpdate(message: NotificationCountUpdate) { - const currentNotificationCount = this.notificationCountSubject.value; - if (message.sequenceNumber <= currentNotificationCount.sequenceNumber) { - return; - } - if (this.zone) { - this.zone.run( - () => { - this.notificationCountSubject.next(message); - } - ); - } else { - this.notificationCountSubject.next(message); - } - } - - public complete() { - this.notificationCountSubject.complete(); - this.notificationsSubject.complete(); - super.complete(); - } - - onNotificationsUpdate(message: NotificationsUpdate) { - const currentNotifications = this.notificationsSubject.value; - if (message.sequenceNumber <= currentNotifications.sequenceNumber) { - message.totalUnreadCount = currentNotifications.totalUnreadCount; - } - let processMessage = message; - if (isDefinedAndNotNull(currentNotifications) && message.update) { - currentNotifications.notifications.unshift(message.update); - if (currentNotifications.notifications.length > this.messageLimit) { - currentNotifications.notifications.pop(); - } - processMessage = currentNotifications; - processMessage.totalUnreadCount = message.totalUnreadCount; - } - if (this.zone) { - this.zone.run( - () => { - this.notificationsSubject.next(processMessage); - this.notificationCountSubject.next(processMessage); - } - ); - } else { - this.notificationsSubject.next(processMessage); - this.notificationCountSubject.next(processMessage); - } - } -} - -export class UnreadCountSubCmd implements WebsocketCmd { - cmdId: number; - type = WsCmdType.NOTIFICATIONS_COUNT; -} - -export class UnreadSubCmd implements WebsocketCmd { - limit: number; - cmdId: number; - type = WsCmdType.NOTIFICATIONS; - - constructor(limit = 10) { - this.limit = limit; - } -} - -export class UnsubscribeCmd implements WebsocketCmd { - cmdId: number; - type = WsCmdType.NOTIFICATIONS_UNSUBSCRIBE; -} - -export class MarkAsReadCmd implements WebsocketCmd { - - cmdId: number; - notifications: string[]; - type = WsCmdType.MARK_NOTIFICATIONS_AS_READ; - - constructor(ids: string[]) { - this.notifications = ids; - } -} - -export class MarkAllAsReadCmd implements WebsocketCmd { - cmdId: number; - type = WsCmdType.MARK_ALL_NOTIFICATIONS_AS_READ; -} - -export interface NotificationCountUpdateMsg extends CmdUpdateMsg { - cmdUpdateType: CmdUpdateType.NOTIFICATIONS_COUNT; - totalUnreadCount: number; - sequenceNumber: number; -} - -export interface NotificationsUpdateMsg extends CmdUpdateMsg { - cmdUpdateType: CmdUpdateType.NOTIFICATIONS; - update?: Notification; - notifications?: Notification[]; - totalUnreadCount: number; - sequenceNumber: number; -} - -export const isNotificationCountUpdateMsg = (message: WebsocketDataMsg): message is NotificationCountUpdateMsg => { - const updateMsg = (message as CmdUpdateMsg); - return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS_COUNT; -}; - -export const isNotificationsUpdateMsg = (message: WebsocketDataMsg): message is NotificationsUpdateMsg => { - const updateMsg = (message as CmdUpdateMsg); - return updateMsg.cmdId !== undefined && updateMsg.cmdUpdateType === CmdUpdateType.NOTIFICATIONS; -}; From 5d7ba1f88bec1da4d6bbb578a8aa79287ec88ea4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 11 Dec 2023 13:50:24 +0200 Subject: [PATCH 14/14] Handle authentication in the same cmds packet. --- .../server/controller/plugin/TbWebSocketHandler.java | 1 + ui-ngx/src/app/core/ws/websocket.service.ts | 9 +-------- .../src/app/shared/models/telemetry/telemetry.models.ts | 9 +++++++++ .../src/app/shared/models/websocket/websocket.models.ts | 1 + 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 0cdb82b612..9274b989dd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -188,6 +188,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke sessionRef.setSecurityCtx(securityCtx); pendingSessions.invalidate(sessionMd.session.getId()); establishSession(sessionMd.session, sessionRef, sessionMd); + webSocketService.handleCommands(sessionRef, cmdsWrapper); } } diff --git a/ui-ngx/src/app/core/ws/websocket.service.ts b/ui-ngx/src/app/core/ws/websocket.service.ts index 82b2ec2453..fa66c49c33 100644 --- a/ui-ngx/src/app/core/ws/websocket.service.ts +++ b/ui-ngx/src/app/core/ws/websocket.service.ts @@ -22,7 +22,6 @@ import { NgZone } from '@angular/core'; import { selectIsAuthenticated } from '@core/auth/auth.selectors'; import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { - AuthCmd, AuthWsCmd, CmdUpdateMsg, NotificationSubscriber, @@ -196,7 +195,7 @@ export abstract class WebsocketService implements WsServ private onOpen(token: string) { this.isOpening = false; this.isOpened = true; - this.dataStream.next(this.createdAuthMsg(token)); + this.cmdWrapper.setAuth(token); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; @@ -266,10 +265,4 @@ export abstract class WebsocketService implements WsServ message, type: 'error' })); } - - private createdAuthMsg(token: string): AuthWsCmd { - return { - authCmd: new AuthCmd(token) - }; - } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 03c7b5e267..e56941f688 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -348,6 +348,7 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { } cmds: Array; + authCmd: AuthCmd; private static popCmds(cmds: Array, leftCount: number): Array { const toPublish = Math.min(cmds.length, leftCount); @@ -358,6 +359,10 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { } } + public setAuth(token: string) { + this.authCmd = new AuthCmd(token); + } + public hasCommands(): boolean { return this.cmds.length > 0; } @@ -368,6 +373,10 @@ export class TelemetryPluginCmdsWrapper implements CmdWrapper { public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper { const preparedWrapper = new TelemetryPluginCmdsWrapper(); + if (this.authCmd) { + preparedWrapper.authCmd = this.authCmd; + this.authCmd = null; + } preparedWrapper.cmds = TelemetryPluginCmdsWrapper.popCmds(this.cmds, maxCommands); return preparedWrapper; } diff --git a/ui-ngx/src/app/shared/models/websocket/websocket.models.ts b/ui-ngx/src/app/shared/models/websocket/websocket.models.ts index fc7fc3d15c..9b88692b51 100644 --- a/ui-ngx/src/app/shared/models/websocket/websocket.models.ts +++ b/ui-ngx/src/app/shared/models/websocket/websocket.models.ts @@ -25,6 +25,7 @@ export interface WsService { } export abstract class CmdWrapper { + abstract setAuth(token: string); abstract hasCommands(): boolean; abstract clear(): void; abstract preparePublishCommands(maxCommands: number): CmdWrapper;