Browse Source

Merge branch 'feature/notification-system' of github.com:ViacheslavKlimov/thingsboard into feature/notification-system

pull/7925/head
Andrii Shvaika 4 years ago
parent
commit
509da5dee4
  1. 4
      application/pom.xml
  2. 80
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  3. 15
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  4. 17
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 2
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  6. 2
      application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java
  7. 337
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  8. 174
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  9. 90
      application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
  10. 118
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  11. 69
      application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
  12. 2
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  13. 4
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  14. 118
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  15. 2
      application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java
  16. 2
      application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java
  17. 2
      application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java
  18. 2
      application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java
  19. 2
      application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java
  20. 2
      application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java
  21. 2
      application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java
  22. 29
      application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java
  23. 5
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  25. 278
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java
  26. 180
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java
  27. 166
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java
  28. 101
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  29. 28
      application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java
  30. 25
      application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java
  31. 49
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  32. 29
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  33. 77
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  34. 51
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  35. 5
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  36. 64
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  37. 6
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  38. 7
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  39. 2
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  40. 8
      application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
  41. 6
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  42. 3
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  43. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  44. 153
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  45. 8
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  46. 88
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  47. 45
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  48. 31
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  49. 2
      application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java
  51. 6
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  52. 14
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  53. 19
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  54. 16
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  55. 9
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java
  56. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java
  57. 10
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
  58. 18
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  59. 17
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java
  60. 7
      application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
  61. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java
  62. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java
  63. 92
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  64. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java
  65. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  66. 2
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  67. 2
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
  68. 48
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  69. 57
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  70. 93
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  71. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
  72. 71
      application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java
  73. 290
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  74. 2
      application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java
  75. 11
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java
  76. 15
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
  77. 30
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java
  78. 38
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java
  79. 10
      application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java
  80. 232
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  81. 34
      application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java
  82. 31
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java
  83. 31
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java
  84. 27
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java
  85. 28
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java
  86. 28
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java
  87. 46
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java
  88. 55
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java
  89. 20
      application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java
  90. 34
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java
  91. 31
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java
  92. 47
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java
  93. 81
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java
  94. 36
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java
  95. 2
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java
  96. 5
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java
  97. 20
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java
  98. 4
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java
  99. 2
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java
  100. 4
      application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java

4
application/pom.xml

@ -360,6 +360,10 @@
<groupId>org.jboss.aerogear</groupId>
<artifactId>aerogear-otp-java</artifactId>
</dependency>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
</dependency>
</dependencies>
<build>

80
application/src/main/data/upgrade/3.4.3/schema_update.sql

@ -0,0 +1,80 @@
--
-- Copyright © 2016-2022 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.
--
CREATE TABLE IF NOT EXISTS notification_target (
id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY,
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC);
CREATE TABLE IF NOT EXISTS notification_template (
id UUID NOT NULL CONSTRAINT notification_template_pkey PRIMARY KEY,
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL
);
CREATE TABLE IF NOT EXISTS notification_rule (
id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY,
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255) NOT NULL,
configuration VARCHAR(2000) NOT NULL
);
CREATE TABLE IF NOT EXISTS notification_request (
id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY,
created_time BIGINT NOT NULL,
tenant_id UUID NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
targets VARCHAR(255) NOT NULL,
template_id UUID NOT NULL,
info VARCHAR(1000),
delivery_methods VARCHAR(255),
additional_config VARCHAR(1000),
originator_type VARCHAR(32) NOT NULL,
originator_entity_id UUID,
originator_entity_type VARCHAR(32),
rule_id UUID NULL,
status VARCHAR(32),
stats VARCHAR(1000)
);
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_type, created_time DESC);
CREATE TABLE IF NOT EXISTS notification (
id UUID NOT NULL,
created_time BIGINT NOT NULL,
request_id UUID NOT NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE,
recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(255) NOT NULL,
text VARCHAR(1000) NOT NULL,
info VARCHAR(1000),
originator_type VARCHAR(32) NOT NULL,
status VARCHAR(32)
) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id);
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID;
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);

15
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -30,7 +30,9 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
@ -89,6 +91,7 @@ import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
@ -302,6 +305,10 @@ public class ActorSystemContext {
@Getter
private ExternalCallExecutorService externalCallExecutorService;
@Autowired
@Getter
private NotificationExecutorService notificationExecutor;
@Autowired
@Getter
private SharedEventLoopGroupService sharedEventLoopGroupService;
@ -318,6 +325,14 @@ public class ActorSystemContext {
@Getter
private SmsSenderFactory smsSenderFactory;
@Autowired
@Getter
private NotificationManager notificationManager;
@Autowired
@Getter
private SlackService slackService;
@Lazy
@Autowired(required = false)
@Getter

17
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -25,6 +25,7 @@ import org.bouncycastle.util.Arrays;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService;
import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache;
@ -35,6 +36,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.rule.engine.util.TenantIdLoader;
import org.thingsboard.server.actors.ActorSystemContext;
@ -470,6 +472,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getExternalCallExecutorService();
}
@Override
public ListeningExecutor getNotificationExecutor() {
return mainCtx.getNotificationExecutor();
}
@Override
@Deprecated
public ScriptEngine createJsScriptEngine(String script, String... argNames) {
@ -679,6 +686,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getSmsSenderFactory();
}
@Override
public NotificationManager getNotificationManager() {
return mainCtx.getNotificationManager();
}
@Override
public SlackService getSlackService() {
return mainCtx.getSlackService();
}
@Override
public RuleEngineRpcService getRpcService() {
return mainCtx.getTbRuleEngineDeviceRpcService();

2
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -25,6 +25,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.security.Authority;
@ -138,6 +139,7 @@ public class SwaggerConfiguration {
)
.securitySchemes(newArrayList(httpLogin()))
.securityContexts(newArrayList(securityContext()))
.ignoredParameterTypes(AuthenticationPrincipal.class)
.enableUrlTemplating(true);
}

2
application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java

@ -43,7 +43,7 @@ import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity;
import org.thingsboard.server.exception.ToErrorResponseEntity;
import javax.annotation.Nullable;
import java.util.Optional;

337
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
@ -58,6 +59,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -75,6 +77,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RpcId;
@ -83,6 +86,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
@ -96,6 +100,7 @@ import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.asset.AssetProfileService;
@ -131,6 +136,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.entitiy.TbNotificationEntityService;
@ -143,6 +149,7 @@ import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
@ -153,6 +160,8 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID;
@ -284,12 +293,18 @@ public abstract class BaseController {
@Autowired
protected TbNotificationEntityService notificationEntityService;
@Autowired
protected EntityActionService entityActionService;
@Autowired
protected QueueService queueService;
@Autowired
protected EntitiesVersionControlService vcService;
@Autowired
protected ExportableEntitiesService entitiesService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -362,11 +377,17 @@ public abstract class BaseController {
* */
@ExceptionHandler(MethodArgumentNotValidException.class)
public void handleValidationError(MethodArgumentNotValidException e, HttpServletResponse response) {
String errorMessage = "Validation error: " + e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
String errorMessage = "Validation error: " + e.getFieldErrors().stream()
.map(fieldError -> {
String property = fieldError.getField();
if (property.equals("valid") || StringUtils.endsWith(property, ".valid")) { // when custom @AssertTrue is used
property = "";
}
return (!property.isEmpty() ? (property + " ") : "") + fieldError.getDefaultMessage();
})
.collect(Collectors.joining(", "));
ThingsboardException thingsboardException = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
handleThingsboardException(thingsboardException, response);
handleControllerException(thingsboardException, response);
}
<T> T checkNotNull(T reference) throws ThingsboardException {
@ -452,27 +473,11 @@ public abstract class BaseController {
}
Tenant checkTenantId(TenantId tenantId, Operation operation) throws ThingsboardException {
try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Tenant tenant = tenantService.findTenantById(tenantId);
checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(tenantId, (t, i) -> tenantService.findTenantById(tenantId), operation);
}
TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException {
try {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
TenantInfo tenant = tenantService.findTenantInfoById(tenantId);
checkNotNull(tenant, "Tenant with id [" + tenantId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
return tenant;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(tenantId, (t, i) -> tenantService.findTenantInfoById(tenantId), operation);
}
TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
@ -492,33 +497,16 @@ public abstract class BaseController {
}
Customer checkCustomerId(CustomerId customerId, Operation operation) throws ThingsboardException {
try {
validateId(customerId, "Incorrect customerId " + customerId);
Customer customer = customerService.findCustomerById(getTenantId(), customerId);
checkNotNull(customer, "Customer with id [" + customerId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customerId, customer);
return customer;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(customerId, customerService::findCustomerById, operation);
}
User checkUserId(UserId userId, Operation operation) throws ThingsboardException {
try {
validateId(userId, "Incorrect userId " + userId);
User user = userService.findUserById(getCurrentUser().getTenantId(), userId);
checkNotNull(user, "User with id [" + userId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation, userId, user);
return user;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(userId, userService::findUserById, operation);
}
protected <I extends EntityId, T extends HasTenantId> void checkEntity(I entityId, T entity, Resource resource) throws ThingsboardException {
if (entityId == null) {
accessControlService
.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity);
accessControlService.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity);
} else {
checkEntityId(entityId, Operation.WRITE);
}
@ -589,203 +577,90 @@ public abstract class BaseController {
checkQueueId(new QueueId(entityId.getId()), operation);
return;
default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation);
}
} catch (Exception e) {
throw handleException(e, false);
}
}
Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException {
protected <E extends HasId<I> & HasTenantId, I extends EntityId> E checkEntityId(I entityId, ThrowingBiFunction<TenantId, I, E> findingFunction, Operation operation) throws ThingsboardException {
try {
validateId(deviceId, "Incorrect deviceId " + deviceId);
Device device = deviceService.findDeviceById(getCurrentUser().getTenantId(), deviceId);
checkNotNull(device, "Device with id [" + deviceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device);
return device;
validateId((UUIDBased) entityId, "Invalid " + entityId.getClass().getSimpleName().toLowerCase());
SecurityUser user = getCurrentUser();
E entity = findingFunction.apply(user.getTenantId(), entityId);
checkNotNull(entity, entity.getClass().getSimpleName() + " with id [" + entityId + "] not found");
accessControlService.checkPermission(user, Resource.of(entityId.getEntityType()), operation, entityId, entity);
return entity;
} catch (Exception e) {
throw handleException(e, false);
}
}
Device checkDeviceId(DeviceId deviceId, Operation operation) throws ThingsboardException {
return checkEntityId(deviceId, deviceService::findDeviceById, operation);
}
DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException {
try {
validateId(deviceId, "Incorrect deviceId " + deviceId);
DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId);
checkNotNull(device, "Device with id [" + deviceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device);
return device;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(deviceId, deviceService::findDeviceInfoById, operation);
}
DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException {
try {
validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId);
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId);
checkNotNull(deviceProfile, "Device profile with id [" + deviceProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile);
return deviceProfile;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(deviceProfileId, deviceProfileService::findDeviceProfileById, operation);
}
protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityView entityView = entityViewService.findEntityViewById(getCurrentUser().getTenantId(), entityViewId);
checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
return entityView;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(entityViewId, entityViewService::findEntityViewById, operation);
}
EntityViewInfo checkEntityViewInfoId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
try {
validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId);
checkNotNull(entityView, "Entity view with id [" + entityViewId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView);
return entityView;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(entityViewId, entityViewService::findEntityViewInfoById, operation);
}
Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException {
try {
validateId(assetId, "Incorrect assetId " + assetId);
Asset asset = assetService.findAssetById(getCurrentUser().getTenantId(), assetId);
checkNotNull(asset, "Asset with id [" + assetId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
return asset;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(assetId, assetService::findAssetById, operation);
}
AssetInfo checkAssetInfoId(AssetId assetId, Operation operation) throws ThingsboardException {
try {
validateId(assetId, "Incorrect assetId " + assetId);
AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId);
checkNotNull(asset, "Asset with id [" + assetId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset);
return asset;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(assetId, assetService::findAssetInfoById, operation);
}
AssetProfile checkAssetProfileId(AssetProfileId assetProfileId, Operation operation) throws ThingsboardException {
try {
validateId(assetProfileId, "Incorrect assetProfileId " + assetProfileId);
AssetProfile assetProfile = assetProfileService.findAssetProfileById(getCurrentUser().getTenantId(), assetProfileId);
checkNotNull(assetProfile, "Asset profile with id [" + assetProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET_PROFILE, operation, assetProfileId, assetProfile);
return assetProfile;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(assetProfileId, assetProfileService::findAssetProfileById, operation);
}
Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);
Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarm);
return alarm;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(alarmId, alarmService::findAlarmById, operation);
}
AlarmInfo checkAlarmInfoId(AlarmId alarmId, Operation operation) throws ThingsboardException {
try {
validateId(alarmId, "Incorrect alarmId " + alarmId);
AlarmInfo alarmInfo = alarmService.findAlarmInfoByIdAsync(getCurrentUser().getTenantId(), alarmId).get();
checkNotNull(alarmInfo, "Alarm with id [" + alarmId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarmId, alarmInfo);
return alarmInfo;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(alarmId, (tenantId, id) -> {
return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId).get();
}, operation);
}
WidgetsBundle checkWidgetsBundleId(WidgetsBundleId widgetsBundleId, Operation operation) throws ThingsboardException {
try {
validateId(widgetsBundleId, "Incorrect widgetsBundleId " + widgetsBundleId);
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(getCurrentUser().getTenantId(), widgetsBundleId);
checkNotNull(widgetsBundle, "Widgets bundle with id [" + widgetsBundleId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation, widgetsBundleId, widgetsBundle);
return widgetsBundle;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(widgetsBundleId, widgetsBundleService::findWidgetsBundleById, operation);
}
WidgetTypeDetails checkWidgetTypeId(WidgetTypeId widgetTypeId, Operation operation) throws ThingsboardException {
try {
validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(getCurrentUser().getTenantId(), widgetTypeId);
checkNotNull(widgetTypeDetails, "Widget type with id [" + widgetTypeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation, widgetTypeId, widgetTypeDetails);
return widgetTypeDetails;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(widgetTypeId, widgetTypeService::findWidgetTypeDetailsById, operation);
}
Dashboard checkDashboardId(DashboardId dashboardId, Operation operation) throws ThingsboardException {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
Dashboard dashboard = dashboardService.findDashboardById(getCurrentUser().getTenantId(), dashboardId);
checkNotNull(dashboard, "Dashboard with id [" + dashboardId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboard);
return dashboard;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(dashboardId, dashboardService::findDashboardById, operation);
}
Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException {
try {
validateId(edgeId, "Incorrect edgeId " + edgeId);
Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
checkNotNull(edge, "Edge with id [" + edgeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
return edge;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(edgeId, edgeService::findEdgeById, operation);
}
EdgeInfo checkEdgeInfoId(EdgeId edgeId, Operation operation) throws ThingsboardException {
try {
validateId(edgeId, "Incorrect edgeId " + edgeId);
EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId);
checkNotNull(edge, "Edge with id [" + edgeId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
return edge;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(edgeId, edgeService::findEdgeInfoById, operation);
}
DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException {
try {
validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(getCurrentUser().getTenantId(), dashboardId);
checkNotNull(dashboardInfo, "Dashboard with id [" + dashboardId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation, dashboardId, dashboardInfo);
return dashboardInfo;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(dashboardId, dashboardService::findDashboardInfoById, operation);
}
ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
@ -816,11 +691,7 @@ public abstract class BaseController {
}
protected RuleChain checkRuleChain(RuleChainId ruleChainId, Operation operation) throws ThingsboardException {
validateId(ruleChainId, "Incorrect ruleChainId " + ruleChainId);
RuleChain ruleChain = ruleChainService.findRuleChainById(getCurrentUser().getTenantId(), ruleChainId);
checkNotNull(ruleChain, "Rule chain with id [" + ruleChainId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation, ruleChainId, ruleChain);
return ruleChain;
return checkEntityId(ruleChainId, ruleChainService::findRuleChainById, operation);
}
protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException {
@ -832,70 +703,27 @@ public abstract class BaseController {
}
TbResource checkResourceId(TbResourceId resourceId, Operation operation) throws ThingsboardException {
try {
validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResource resource = resourceService.findResourceById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resource, "Resource with id [" + resourceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resource);
return resource;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(resourceId, resourceService::findResourceById, operation);
}
TbResourceInfo checkResourceInfoId(TbResourceId resourceId, Operation operation) throws ThingsboardException {
try {
validateId(resourceId, "Incorrect resourceId " + resourceId);
TbResourceInfo resourceInfo = resourceService.findResourceInfoById(getCurrentUser().getTenantId(), resourceId);
checkNotNull(resourceInfo, "Resource with id [" + resourceId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, resourceId, resourceInfo);
return resourceInfo;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(resourceId, resourceService::findResourceInfoById, operation);
}
OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
try {
validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId);
checkNotNull(otaPackage, "OTA package with id [" + otaPackageId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage);
return otaPackage;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(otaPackageId, otaPackageService::findOtaPackageById, operation);
}
OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException {
try {
validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId);
OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId);
checkNotNull(otaPackageIn, "OTA package with id [" + otaPackageId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn);
return otaPackageIn;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(otaPackageId, otaPackageService::findOtaPackageInfoById, operation);
}
Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException {
try {
validateId(rpcId, "Incorrect rpcId " + rpcId);
Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId);
checkNotNull(rpc, "RPC with id [" + rpcId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc);
return rpc;
} catch (Exception e) {
throw handleException(e, false);
}
return checkEntityId(rpcId, rpcService::findById, operation);
}
protected Queue checkQueueId(QueueId queueId, Operation operation) throws ThingsboardException {
validateId(queueId, "Incorrect queueId " + queueId);
Queue queue = queueService.findQueueById(getCurrentUser().getTenantId(), queueId);
checkNotNull(queue);
accessControlService.checkPermission(getCurrentUser(), Resource.QUEUE, operation, queueId, queue);
Queue queue = checkEntityId(queueId, queueService::findQueueById, operation);
TenantId tenantId = getTenantId();
if (queue.getTenantId().isNullUid() && !tenantId.isNullUid()) {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
@ -915,6 +743,40 @@ public abstract class BaseController {
return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
}
protected <E extends HasName & HasId<? extends EntityId>> void logEntityAction(SecurityUser user, EntityType entityType, E savedEntity, ActionType actionType) {
logEntityAction(user, entityType, null, savedEntity, actionType, null);
}
protected <E extends HasName & HasId<? extends EntityId>> void logEntityAction(SecurityUser user, EntityType entityType, E entity, E savedEntity, ActionType actionType, Exception e) {
EntityId entityId = savedEntity != null ? savedEntity.getId() : emptyId(entityType);
entityActionService.logEntityAction(user, entityId, savedEntity != null ? savedEntity : entity,
user.getCustomerId(), actionType, e);
}
protected <E extends HasName & HasId<? extends EntityId>> E doSaveAndLog(EntityType entityType, E entity, BiFunction<TenantId, E, E> savingFunction) throws Exception {
ActionType actionType = entity.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
SecurityUser user = getCurrentUser();
try {
E savedEntity = savingFunction.apply(user.getTenantId(), entity);
logEntityAction(user, entityType, savedEntity, actionType);
return savedEntity;
} catch (Exception e) {
logEntityAction(user, entityType, entity, null, actionType, e);
throw e;
}
}
protected <E extends HasName & HasId<I>, I extends EntityId> void doDeleteAndLog(EntityType entityType, E entity, BiConsumer<TenantId, I> deleteFunction) throws Exception {
SecurityUser user = getCurrentUser();
try {
deleteFunction.accept(user.getTenantId(), entity.getId());
logEntityAction(user, entityType, entity, ActionType.DELETED);
} catch (Exception e) {
logEntityAction(user, entityType, entity, entity, ActionType.DELETED, e);
throw e;
}
}
protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) {
sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action);
}
@ -959,4 +821,5 @@ public abstract class BaseController {
}, MoreExecutors.directExecutor());
return deferredResult;
}
}

174
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -0,0 +1,174 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@RequiredArgsConstructor
@Slf4j
public class NotificationController extends BaseController {
private final NotificationService notificationService;
private final NotificationRequestService notificationRequestService;
private final NotificationManager notificationManager;
private final NotificationSettingsService notificationSettingsService;
private final SlackService slackService;
@GetMapping("/notifications")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public PageData<Notification> getNotifications(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(defaultValue = "false") boolean unreadOnly,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationService.findNotificationsByUserIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink);
}
@PutMapping("/notification/{id}/read") // or maybe to NotificationUpdateRequest for the future
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void markNotificationAsRead(@PathVariable UUID id,
@AuthenticationPrincipal SecurityUser user) {
NotificationId notificationId = new NotificationId(id);
notificationManager.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId);
}
// delete notification?
@PostMapping("/notification/request")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest,
@AuthenticationPrincipal SecurityUser user) throws Exception {
if (notificationRequest.getId() != null) {
throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it");
}
checkEntity(notificationRequest.getId(), notificationRequest, Resource.NOTIFICATION_REQUEST);
notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN);
notificationRequest.setOriginatorEntityId(user.getId());
notificationRequest.setOriginatorEntity(user);
if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) {
throw new IllegalArgumentException("Unsupported notification info type");
}
notificationRequest.setRuleId(null);
notificationRequest.setStatus(null);
notificationRequest.setStats(null);
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::processNotificationRequest);
}
@GetMapping("/notification/request/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequest getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException {
NotificationRequestId notificationRequestId = new NotificationRequestId(id);
return checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.READ);
}
@GetMapping("/notification/requests")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData<NotificationRequest> getNotificationRequests(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationRequestService.findNotificationRequestsByTenantId(user.getTenantId(), pageLink);
}
@DeleteMapping("/notification/request/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationRequest(@PathVariable UUID id) throws Exception {
NotificationRequestId notificationRequestId = new NotificationRequestId(id);
NotificationRequest notificationRequest = checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestById, Operation.DELETE);
doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest);
}
@PostMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings,
@AuthenticationPrincipal SecurityUser user) {
TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId();
notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings);
return notificationSettings;
}
@GetMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId();
return notificationSettingsService.findNotificationSettings(tenantId);
}
@GetMapping("/notification/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
@AuthenticationPrincipal SecurityUser user) {
NotificationSettings settings = getNotificationSettings(user);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
if (slackConfig == null) {
throw new IllegalArgumentException("Slack is not configured");
}
return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type);
}
}

90
application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java

@ -0,0 +1,90 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping("/api/notification")
@RequiredArgsConstructor
@Slf4j
public class NotificationRuleController extends BaseController {
private final NotificationRuleService notificationRuleService;
@PostMapping("/rule")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public NotificationRule saveNotificationRule(@RequestBody NotificationRule notificationRule) throws Exception {
checkEntity(notificationRule.getId(), notificationRule, Resource.NOTIFICATION_RULE);
return doSaveAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::saveNotificationRule);
}
@GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public NotificationRule getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
NotificationRuleId notificationRuleId = new NotificationRuleId(id);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.READ);
}
@GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public PageData<NotificationRule> getNotificationRules(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationRuleService.findNotificationRulesByTenantId(user.getTenantId(), pageLink);
}
@DeleteMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
public void deleteNotificationRule(@PathVariable UUID id,
@AuthenticationPrincipal SecurityUser user) throws Exception {
NotificationRuleId notificationRuleId = new NotificationRuleId(id);
NotificationRule notificationRule = checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleById, Operation.DELETE);
doDeleteAndLog(EntityType.NOTIFICATION_RULE, notificationRule, notificationRuleService::deleteNotificationRuleById);
tbClusterService.broadcastEntityStateChangeEvent(user.getTenantId(), notificationRuleId, ComponentLifecycleEvent.DELETED);
}
}

118
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -0,0 +1,118 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping("/api/notification")
@RequiredArgsConstructor
@Slf4j
public class NotificationTargetController extends BaseController {
private final NotificationTargetService notificationTargetService;
@PostMapping("/target")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationTarget saveNotificationTarget(@RequestBody @Valid NotificationTarget notificationTarget,
@AuthenticationPrincipal SecurityUser user) throws Exception {
checkEntity(notificationTarget.getId(), notificationTarget, Resource.NOTIFICATION_TARGET);
if (!user.isSystemAdmin()) {
NotificationTargetConfig targetConfig = notificationTarget.getConfiguration();
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink);
}, 200);
for (User recipient : recipients) {
accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient);
}
}
return doSaveAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::saveNotificationTarget);
}
@GetMapping("/target/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationTarget getNotificationTargetById(@PathVariable UUID id) throws ThingsboardException {
NotificationTargetId notificationTargetId = new NotificationTargetId(id);
return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.READ);
}
@PostMapping("/target/recipients")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData<User> getRecipientsForNotificationTargetConfig(@RequestBody NotificationTarget notificationTarget,
@RequestParam int pageSize,
@RequestParam int page,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, null, null, null);
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, notificationTarget.getConfiguration(), pageLink);
if (!user.isSystemAdmin()) {
for (User recipient : recipients.getData()) {
accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipient.getId(), recipient);
}
}
return recipients;
}
@GetMapping("/targets")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData<NotificationTarget> getNotificationTargets(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationTargetService.findNotificationTargetsByTenantId(user.getTenantId(), pageLink);
}
@DeleteMapping("/target/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationTarget(@PathVariable UUID id) throws Exception {
NotificationTargetId notificationTargetId = new NotificationTargetId(id);
NotificationTarget notificationTarget = checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, Operation.DELETE);
doDeleteAndLog(EntityType.NOTIFICATION_TARGET, notificationTarget, notificationTargetService::deleteNotificationTargetById);
}
}

69
application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequiredArgsConstructor
@RequestMapping("/api/notification")
public class NotificationTemplateController extends BaseController {
private final NotificationTemplateService notificationTemplateService;
@PostMapping("/template")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationTemplate saveNotificationTemplate(@RequestBody @Valid NotificationTemplate notificationTemplate) throws Exception {
checkEntity(notificationTemplate.getId(), notificationTemplate, Resource.NOTIFICATION_TEMPLATE);
return doSaveAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::saveNotificationTemplate);
}
@GetMapping("/template/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationTemplate getNotificationTemplateById(@PathVariable UUID id) throws ThingsboardException {
NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id);
return checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.READ);
}
@DeleteMapping("/template/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationTemplate(@PathVariable UUID id) throws Exception {
NotificationTemplateId notificationTemplateId = new NotificationTemplateId(id);
NotificationTemplate notificationTemplate = checkEntityId(notificationTemplateId, notificationTemplateService::findNotificationTemplateById, Operation.DELETE);
doDeleteAndLog(EntityType.NOTIFICATION_TEMPLATE, notificationTemplate, notificationTemplateService::deleteNotificationTemplateById);
}
}

2
application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java

@ -47,7 +47,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rpc.RemoveRpcActorMsg;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity;
import org.thingsboard.server.exception.ToErrorResponseEntity;
import javax.annotation.Nullable;
import java.util.UUID;

4
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -84,8 +84,8 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.telemetry.AttributeData;
import org.thingsboard.server.service.telemetry.TsData;
import org.thingsboard.server.service.telemetry.exception.InvalidParametersException;
import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
import org.thingsboard.server.exception.InvalidParametersException;
import org.thingsboard.server.exception.UncheckedApiException;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;

118
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
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;
@ -40,10 +41,11 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.telemetry.SessionEvent;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
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 javax.websocket.RemoteEndpoint;
import javax.websocket.SendHandler;
@ -53,26 +55,25 @@ import java.io.IOException;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import static org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService.NUMBER_OF_PING_ATTEMPTS;
import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS;
@Service
@TbCoreComponent
@Slf4j
public class TbWebSocketHandler extends TextWebSocketHandler implements TelemetryWebSocketMsgEndpoint {
public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint {
private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService webSocketService;
@Autowired @Lazy
private WebSocketService webSocketService;
@Autowired
private TbTenantProfileCache tenantProfileCache;
@ -82,7 +83,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
@Value("${server.ws.ping_timeout:30000}")
private long pingTimeout;
private ConcurrentMap<String, TelemetryWebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
private ConcurrentMap<String, WebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
private ConcurrentMap<String, TbRateLimits> perSessionUpdateLimits = new ConcurrentHashMap<>();
private ConcurrentMap<TenantId, Set<String>> tenantSessionsMap = new ConcurrentHashMap<>();
@ -133,16 +134,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
String internalSessionId = session.getId();
TelemetryWebSocketSessionRef sessionRef = toRef(session);
WebSocketSessionRef sessionRef = toRef(session);
String externalSessionId = sessionRef.getSessionId();
if (!checkLimits(session, sessionRef)) {
return;
}
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef,
tenantProfileConfiguration != null && tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ?
tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500));
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef));
externalSessionMap.put(externalSessionId, internalSessionId);
processInWebSocketService(sessionRef, SessionEvent.onEstablished());
@ -182,7 +180,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) {
try {
webSocketService.handleWebSocketSessionEvent(sessionRef, event);
} catch (BeanCreationNotAllowedException e) {
@ -190,7 +188,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private TelemetryWebSocketSessionRef toRef(WebSocketSession session) throws IOException {
private WebSocketSessionRef toRef(WebSocketSession session) throws IOException {
URI sessionUri = session.getUri();
String path = sessionUri.getPath();
path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
@ -199,35 +197,39 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
String[] pathElements = path.split("/");
String serviceToken = pathElements[0];
if (!"telemetry".equalsIgnoreCase(serviceToken)) {
throw new InvalidParameterException("Can't find plugin with specified token!");
} else {
SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal();
return new TelemetryWebSocketSessionRef(UUID.randomUUID().toString(), currentUser, session.getLocalAddress(), session.getRemoteAddress());
}
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()
.sessionId(UUID.randomUUID().toString())
.securityCtx(currentUser)
.localAddress(session.getLocalAddress())
.remoteAddress(session.getRemoteAddress())
.sessionType(sessionType)
.build();
}
private class SessionMetaData implements SendHandler {
private final WebSocketSession session;
private final RemoteEndpoint.Async asyncRemote;
private final TelemetryWebSocketSessionRef sessionRef;
private final WebSocketSessionRef sessionRef;
private volatile boolean isSending = false;
private final Queue<TbWebSocketMsg<?>> msgQueue;
// TODO: carefully review ( + discuss removal of the msgQueue)
private final Semaphore sendingSemaphore = new Semaphore(1);
private volatile long lastActivityTime;
SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef 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();
}
synchronized void sendPing(long currentTime) {
void sendPing(long currentTime) {
try {
long timeSinceLastActivity = currentTime - lastActivityTime;
if (timeSinceLastActivity >= pingTimeout) {
@ -242,41 +244,37 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private void closeSession(CloseStatus reason) {
void closeSession(CloseStatus reason) {
try {
close(this.sessionRef, reason);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
} finally {
sendingSemaphore.release();
}
}
synchronized void processPongMessage(long currentTime) {
void processPongMessage(long currentTime) {
lastActivityTime = currentTime;
}
synchronized void sendMsg(String msg) {
void sendMsg(String msg) {
sendMsg(new TbWebSocketTextMsg(msg));
}
synchronized void sendMsg(TbWebSocketMsg<?> msg) {
if (isSending) {
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());
}
closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!"));
}
} else {
isSending = true;
void sendMsg(TbWebSocketMsg<?> msg) {
try {
sendingSemaphore.acquire();
sendMsgInternal(msg);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (Exception e) {
sendingSemaphore.release();
throw e;
}
}
private void sendMsgInternal(TbWebSocketMsg<?> msg) {
void sendMsgInternal(TbWebSocketMsg<?> msg) {
try {
if (TbWebSocketMsgType.TEXT.equals(msg.getType())) {
TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg;
@ -284,7 +282,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} else {
TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg;
this.asyncRemote.sendPing(pingMsg.getMsg());
processNextMsg();
}
} catch (Exception e) {
log.trace("[{}] Failed to send msg", session.getId(), e);
@ -298,22 +295,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.trace("[{}] Failed to send msg", session.getId(), result.getException());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else {
processNextMsg();
}
}
private void processNextMsg() {
TbWebSocketMsg<?> msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
} else {
isSending = false;
sendingSemaphore.release();
}
}
}
@Override
public void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException {
public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException {
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing {}", externalId, msg);
String internalId = externalSessionMap.get(externalId);
@ -349,7 +337,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
@Override
public void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException {
public void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException {
String externalId = sessionRef.getSessionId();
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
@ -365,7 +353,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
@Override
public void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
public void close(WebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing close request", externalId);
String internalId = externalSessionMap.get(externalId);
@ -381,7 +369,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private boolean checkLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) throws Exception {
private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) {
return true;
@ -448,7 +436,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
return true;
}
private void cleanupLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) {
private void cleanupLimits(WebSocketSession session, WebSocketSessionRef sessionRef) {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) return;
@ -483,7 +471,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) {
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) {
return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()))
.map(TenantProfile::getDefaultProfileConfiguration).orElse(null);
}

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/AccessDeniedException.java → application/src/main/java/org/thingsboard/server/exception/AccessDeniedException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/EntityNotFoundException.java → application/src/main/java/org/thingsboard/server/exception/EntityNotFoundException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/InternalErrorException.java → application/src/main/java/org/thingsboard/server/exception/InternalErrorException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/InvalidParametersException.java → application/src/main/java/org/thingsboard/server/exception/InvalidParametersException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/ToErrorResponseEntity.java → application/src/main/java/org/thingsboard/server/exception/ToErrorResponseEntity.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/UnauthorizedException.java → application/src/main/java/org/thingsboard/server/exception/UnauthorizedException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

2
application/src/main/java/org/thingsboard/server/service/telemetry/exception/UncheckedApiException.java → application/src/main/java/org/thingsboard/server/exception/UncheckedApiException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.exception;
package org.thingsboard.server.exception;
import org.springframework.http.ResponseEntity;

29
application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2022 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.executors;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.AbstractListeningExecutor;
@Component
public class NotificationExecutorService extends AbstractListeningExecutor {
@Override
protected int getThreadPollSize() {
return 10; // FIXME [viacheslav]
}
}

5
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -671,4 +671,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
}
}
@Override
public void createNotificationConfigs() {
// create default notification targets: Alarm's customer
}
}

3
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -38,4 +38,7 @@ public interface SystemDataLoaderService {
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
void createQueues();
void createNotificationConfigs();
}

278
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java

@ -0,0 +1,278 @@
/**
* Copyright © 2016-2022 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.notification;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.notification.channels.NotificationChannel;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import org.thingsboard.server.service.telemetry.AbstractSubscriptionService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings("UnstableApiUsage")
public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager, NotificationChannel {
private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService;
private final NotificationService notificationService;
private final NotificationTemplateService notificationTemplateService;
private final NotificationSettingsService notificationSettingsService;
private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider;
private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> {
if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) {
throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured");
}
});
if (notificationRequest.getAdditionalConfig() != null) {
NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId());
return savedNotificationRequest;
}
}
notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(savedNotificationRequest)
.settings(settings)
.build();
ctx.init(notificationTemplateService);
for (NotificationTargetId targetId : notificationRequest.getTargets()) {
DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getOriginatorCustomerId(), targetId, pageLink);
}, 200, recipientsBatch -> {
List<ListenableFuture<Void>> results = new ArrayList<>();
for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) {
NotificationChannel notificationChannel = channels.get(deliveryMethod);
log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId());
List<User> recipients = recipientsBatch.getData();
for (User recipient : recipients) {
ListenableFuture<Void> resultFuture = processForRecipient(notificationChannel, recipient, ctx);
DonAsynchron.withCallback(resultFuture, result -> {
ctx.getStats().reportSent(deliveryMethod, recipient);
}, error -> {
ctx.getStats().reportError(deliveryMethod, recipient, error);
});
results.add(resultFuture);
}
}
Futures.allAsList(results).addListener(() -> {
try {
notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
} catch (Exception e) {
log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e);
}
}, dbCallbackExecutorService);
});
}
return savedNotificationRequest;
}
private ListenableFuture<Void> processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) {
NotificationDeliveryMethod deliveryMethod = notificationChannel.getDeliveryMethod();
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
return Futures.immediateFailedFuture(new AlreadySentException());
}
String text;
try {
DeliveryMethodNotificationTemplate template = ctx.getTemplate(deliveryMethod);
text = TbNodeUtils.processTemplate(template.getBody(), ctx.createTemplateContext(recipient));
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
return notificationChannel.sendNotification(recipient, text, ctx);
}
private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) {
TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits())
.setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits())
.setTs(System.currentTimeMillis());
TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder()
.setNotificationSchedulerServiceMsg(msg)
.build();
clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null);
}
@Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
NotificationRequest request = ctx.getRequest();
log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId());
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationTemplate().getNotificationType())
.text(text)
.info(request.getInfo())
.originatorType(request.getOriginatorType())
.status(NotificationStatus.SENT)
.build();
try {
notification = notificationService.saveNotification(recipient.getTenantId(), notification);
} catch (Exception e) {
log.error("Failed to create notification for recipient {}", recipient.getId(), e);
return Futures.immediateFailedFuture(e);
}
return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true);
}
@Override
public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId);
if (updated) {
log.debug("Marking notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
Notification notification = notificationService.findNotificationById(tenantId, notificationId);
onNotificationUpdate(tenantId, recipientId, notification, false);
}
}
@Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) {
log.debug("Deleting notification request {}", notificationRequestId);
notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId);
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId)
.deleted(true)
.build());
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
}
@Override
public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Updating notification request {}", notificationRequest.getId());
notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequest.getId())
.notificationInfo(notificationRequest.getInfo())
.deleted(false)
.build());
return notificationRequest;
}
private ListenableFuture<Void> onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) {
NotificationUpdate update = NotificationUpdate.builder()
.notification(notification)
.isNew(isNew)
.build();
log.trace("Submitting notification update for recipient {}: {}", recipientId, update);
return Futures.submit(() -> {
forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> {
subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY);
}, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update));
}, wsCallBackExecutor);
}
private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) {
// todo: check delivery method
log.trace("Submitting notification request update: {}", update);
wsCallBackExecutor.submit(() -> {
TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update);
Set<String> coreServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_CORE));
for (String serviceId : coreServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId);
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), notificationRequestUpdateProto), null);
}
});
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.WEBSOCKET;
}
@Override
protected String getExecutorPrefix() {
return "notification";
}
@Autowired
public void setChannels(List<NotificationChannel> channels, NotificationManager websocketNotificationChannel) {
this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c));
this.channels.put(NotificationDeliveryMethod.WEBSOCKET, (NotificationChannel) websocketNotificationChannel);
}
}

180
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java

@ -0,0 +1,180 @@
/**
* Copyright © 2016-2022 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.notification;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import java.util.List;
import java.util.Map;
@Service
@TbCoreComponent
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService {
private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService;
@Autowired @Lazy
private NotificationManager notificationManager;
private final NotificationExecutorService notificationExecutor;
@Override
public ListenableFuture<Void> onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) {
return processAlarmUpdate(tenantId, alarm, false);
}
@Override
public ListenableFuture<Void> onAlarmDeleted(TenantId tenantId, Alarm alarm) {
return processAlarmUpdate(tenantId, alarm, true);
}
private ListenableFuture<Void> processAlarmUpdate(TenantId tenantId, Alarm alarm, boolean deleted) {
if (alarm.getNotificationRuleId() == null) return Futures.immediateFuture(null);
return notificationExecutor.submit(() -> {
onAlarmUpdate(tenantId, alarm.getNotificationRuleId(), alarm, deleted);
return null;
});
}
private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm, boolean deleted) {
log.debug("Processing alarm update ({}) with notification rule {}", alarm.getId(), notificationRuleId);
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId());
NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId);
if (notificationRule == null) return;
if (alarmAcknowledged(alarm) || deleted) {
if (notificationRequests.isEmpty()) {
return;
}
for (NotificationRequest notificationRequest : notificationRequests) {
if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) {
notificationManager.deleteNotificationRequest(tenantId, notificationRequest.getId());
}
}
}
if (notificationRequests.isEmpty()) {
NotificationRuleConfig config = notificationRule.getConfiguration();
NotificationTargetId initialNotificationTargetId = config.getInitialNotificationTargetId();
if (initialNotificationTargetId != null) {
submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0);
}
if (config.getEscalationConfig() != null) {
for (NonConfirmedNotificationEscalation escalation : config.getEscalationConfig().getEscalations()) {
submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInSec());
}
}
} else {
NotificationInfo newNotificationInfo = constructNotificationInfo(alarm);
for (NotificationRequest notificationRequest : notificationRequests) {
NotificationInfo previousNotificationInfo = notificationRequest.getInfo();
if (!previousNotificationInfo.equals(newNotificationInfo)) {
notificationRequest.setInfo(newNotificationInfo);
notificationManager.updateNotificationRequest(tenantId, notificationRequest);
}
}
}
}
private boolean alarmAcknowledged(Alarm alarm) {
return alarm.getStatus().isAck() && alarm.getStatus().isCleared();
}
private void submitNotificationRequest(TenantId tenantId, NotificationTargetId targetId, NotificationRule notificationRule, Alarm alarm, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec);
}
NotificationInfo notificationInfo = constructNotificationInfo(alarm);
Map<String, String> templateContext = Map.of(
"alarmType", alarm.getType(),
"alarmId", alarm.getId().toString(),
"alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(),
"alarmOriginatorId", alarm.getOriginator().getId().toString()
);
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.targets(List.of(targetId))
.templateId(notificationRule.getTemplateId())
.deliveryMethods(notificationRule.getDeliveryMethods())
.additionalConfig(config)
.info(notificationInfo)
.ruleId(notificationRule.getId())
.originatorType(NotificationOriginatorType.ALARM)
.originatorEntityId(alarm.getId())
.originatorEntity(alarm)
.templateContext(templateContext)
.build();
notificationManager.processNotificationRequest(tenantId, notificationRequest);
}
private NotificationInfo constructNotificationInfo(Alarm alarm) {
// TODO: add info about assignee
return AlarmOriginatedNotificationInfo.builder()
.alarmId(alarm.getId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.build();
}
@EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
componentLifecycleMsg.getEntityId().getEntityType() != EntityType.NOTIFICATION_RULE) {
return;
}
TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationManager.deleteNotificationRequest(tenantId, notificationRequestId);
}
}
}

166
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java

@ -0,0 +1,166 @@
/**
* Copyright © 2016-2022 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.notification;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@TbCoreComponent
@Service
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings("UnstableApiUsage")
public class DefaultNotificationSchedulerService extends AbstractPartitionBasedService<NotificationRequestId> implements NotificationSchedulerService {
private final NotificationManager notificationManager;
private final NotificationRequestService notificationRequestService;
private final SchedulerComponent scheduler;
private final NotificationExecutorService notificationExecutor;
private final Map<NotificationRequestId, ScheduledRequestMetadata> scheduledNotificationRequests = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
super.init();
}
@Override
protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
PageDataIterable<NotificationRequest> notificationRequests = new PageDataIterable<>(pageLink -> {
return notificationRequestService.findScheduledNotificationRequests(pageLink);
}, 1000);
for (NotificationRequest notificationRequest : notificationRequests) {
TopicPartitionInfo requestPartition = partitionService.resolve(ServiceType.TB_CORE, notificationRequest.getTenantId(), notificationRequest.getId());
if (addedPartitions.contains(requestPartition)) {
partitionedEntities.computeIfAbsent(requestPartition, k -> ConcurrentHashMap.newKeySet()).add(notificationRequest.getId());
if (!scheduledNotificationRequests.containsKey(notificationRequest.getId())) {
scheduleNotificationRequest(notificationRequest.getTenantId(), notificationRequest, notificationRequest.getCreatedTime());
}
}
}
return Collections.emptyMap();
}
@Override
public void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs) {
NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);
scheduleNotificationRequest(tenantId, notificationRequest, requestTs);
}
private void scheduleNotificationRequest(TenantId tenantId, NotificationRequest request, long requestTs) {
int delayInSec = Optional.ofNullable(request)
.map(NotificationRequest::getAdditionalConfig)
.map(NotificationRequestConfig::getSendingDelayInSec)
.orElse(0);
if (delayInSec <= 0) return;
long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs);
if (delayInMs < 0) {
delayInMs = 0;
}
ScheduledFuture<?> scheduledTask = scheduler.schedule(() -> {
NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, request.getId());
if (notificationRequest == null) return;
notificationExecutor.executeAsync(() -> {
notificationManager.processNotificationRequest(tenantId, notificationRequest);
});
scheduledNotificationRequests.remove(notificationRequest.getId());
}, delayInMs, TimeUnit.MILLISECONDS);
scheduledNotificationRequests.put(request.getId(), new ScheduledRequestMetadata(tenantId, scheduledTask));
}
@EventListener(ComponentLifecycleMsg.class)
public void handleComponentLifecycleEvent(ComponentLifecycleMsg event) {
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
EntityId entityId = event.getEntityId();
switch (entityId.getEntityType()) {
case NOTIFICATION_REQUEST:
cancelAndRemove((NotificationRequestId) entityId);
break;
case TENANT:
Set<NotificationRequestId> toCancel = new HashSet<>();
scheduledNotificationRequests.forEach((notificationRequestId, scheduledRequestMetadata) -> {
if (scheduledRequestMetadata.getTenantId().equals(entityId)) {
toCancel.add(notificationRequestId);
}
});
toCancel.forEach(this::cancelAndRemove);
break;
}
}
}
@Override
protected void cleanupEntityOnPartitionRemoval(NotificationRequestId notificationRequestId) {
cancelAndRemove(notificationRequestId);
}
private void cancelAndRemove(NotificationRequestId notificationRequestId) {
ScheduledRequestMetadata md = scheduledNotificationRequests.remove(notificationRequestId);
if (md != null) {
md.getFuture().cancel(false);
}
}
@Override
protected String getServiceName() {
return "Notifications scheduler";
}
@Override
protected String getSchedulerExecutorName() {
return "notifications-scheduler";
}
@Data
private static class ScheduledRequestMetadata {
private final TenantId tenantId;
private final ScheduledFuture<?> future;
}
}

101
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -0,0 +1,101 @@
/**
* Copyright © 2016-2022 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.notification;
import com.google.common.base.Strings;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class NotificationProcessingContext {
@Getter
private final TenantId tenantId;
private final HasCustomerId originatorEntity;
private final NotificationSettings settings;
@Getter
private final NotificationRequest request;
private final Map<String, String> additionalTemplateContext;
@Getter
private NotificationTemplate notificationTemplate;
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter
private final NotificationRequestStats stats;
@Builder
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings) {
this.tenantId = tenantId;
this.originatorEntity = request.getOriginatorEntity();
this.request = request;
this.settings = settings;
this.additionalTemplateContext = request.getTemplateContext();
this.stats = new NotificationRequestStats();
}
public void init(NotificationTemplateService templateService) {
notificationTemplate = templateService.findNotificationTemplateById(tenantId, request.getTemplateId());
NotificationTemplateConfig config = notificationTemplate.getConfiguration();
for (NotificationDeliveryMethod deliveryMethod : request.getDeliveryMethods()) {
DeliveryMethodNotificationTemplate template = config.getTemplates().get(deliveryMethod);
if (StringUtils.isEmpty(template.getBody())) {
template.setBody(config.getDefaultTextTemplate());
}
}
templates = config.getTemplates();
}
public <T extends DeliveryMethodNotificationTemplate> T getTemplate(NotificationDeliveryMethod deliveryMethod) {
return (T) templates.get(deliveryMethod);
}
public <C extends NotificationDeliveryMethodConfig> C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
}
public Map<String, String> createTemplateContext(User recipient) {
Map<String, String> templateContext = new HashMap<>();
templateContext.put("email", recipient.getEmail());
templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName()));
templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName()));
if (additionalTemplateContext != null) {
templateContext.putAll(additionalTemplateContext);
}
return templateContext;
}
public CustomerId getOriginatorCustomerId() {
return originatorEntity != null ? originatorEntity.getCustomerId() : null;
}
}

28
application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.notification;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId;
public interface NotificationRuleProcessingService {
ListenableFuture<Void> onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm);
ListenableFuture<Void> onAlarmDeleted(TenantId tenantId, Alarm alarm);
}

25
application/src/main/java/org/thingsboard/server/service/notification/NotificationSchedulerService.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2022 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.notification;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
public interface NotificationSchedulerService {
void scheduleNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId, long requestTs);
}

49
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2022 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.notification.channels;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@Component
@RequiredArgsConstructor
public class EmailNotificationChannel implements NotificationChannel {
private final MailService mailService;
private final MailExecutorService executor;
@Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
EmailDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.EMAIL);
return executor.submit(() -> {
mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text, template.getSubject());
return null;
});
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.EMAIL;
}
}

29
application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2022 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.notification.channels;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
public interface NotificationChannel {
ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx);
NotificationDeliveryMethod getDeliveryMethod();
}

77
application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2022 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.notification.channels;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@Component
@RequiredArgsConstructor
@SuppressWarnings("UnstableApiUsage")
public class SlackNotificationChannel implements NotificationChannel {
private final SlackService slackService;
private final ExternalCallExecutorService executor;
@Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK);
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
if (StringUtils.isNotEmpty(template.getConversationId())) { // if conversationId is set, we only need to send message once
if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
return Futures.immediateFailedFuture(new AlreadySentException());
} else {
return executor.submit(() -> {
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), template.getConversationId(), text);
return null;
});
}
} else {
if (StringUtils.isNoneEmpty(recipient.getFirstName(), recipient.getLastName())) {
String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
return executor.submit(() -> {
SlackConversation conversation = slackService.findConversation(recipient.getTenantId(), config.getBotToken(), SlackConversation.Type.USER, username);
if (conversation == null) {
throw new IllegalArgumentException("Slack user not found for given name '" + username + "'");
}
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), text);
return null;
});
} else {
return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user"));
}
}
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SLACK;
}
}

51
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -0,0 +1,51 @@
/**
* Copyright © 2016-2022 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.notification.channels;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.service.sms.SmsExecutorService;
@Component
@RequiredArgsConstructor
public class SmsNotificationChannel implements NotificationChannel {
private final SmsService smsService;
private final SmsExecutorService executor;
@Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
String phone = recipient.getPhone();
if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number"));
return executor.submit(() -> {
smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text);
return null;
});
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SMS;
}
}

5
application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java

@ -20,16 +20,17 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -48,6 +49,8 @@ public abstract class AbstractPartitionBasedService<T extends EntityId> extends
protected final ConcurrentMap<TopicPartitionInfo, List<ListenableFuture<?>>> partitionedFetchTasks = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
@Autowired
protected PartitionService partitionService;
protected ListeningScheduledExecutorService scheduledExecutor;
abstract protected String getServiceName();

64
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -18,8 +18,10 @@ package org.thingsboard.server.service.queue;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
@ -27,7 +29,9 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
@ -35,8 +39,6 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -62,9 +64,11 @@ import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.notification.NotificationSchedulerService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -72,12 +76,16 @@ import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import org.thingsboard.server.service.sync.vc.GitVersionControlQueueService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -120,6 +128,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final EdgeNotificationService edgeNotificationService;
private final OtaPackageStateService firmwareStateService;
private final GitVersionControlQueueService vcQueueService;
private final NotificationSchedulerService notificationSchedulerService;
private final TbCoreConsumerStats stats;
protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
@ -145,8 +154,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
OtaPackageStateService firmwareStateService,
GitVersionControlQueueService vcQueueService,
PartitionService partitionService,
Optional<JwtSettingsService> jwtSettingsService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
@ -159,6 +170,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.statsService = statsService;
this.firmwareStateService = firmwareStateService;
this.vcQueueService = vcQueueService;
this.notificationSchedulerService = notificationSchedulerService;
}
@PostConstruct
@ -253,6 +265,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
}
}
callback.onSuccess();
} else if (toCoreMsg.hasNotificationSchedulerServiceMsg()) {
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
}
} catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e);
@ -335,6 +351,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreNotification.hasVcResponseMsg()) {
vcQueueService.processResponse(toCoreNotification.getVcResponseMsg());
callback.onSuccess();
} else if (toCoreNotification.hasToSubscriptionMgrMsg()) {
forwardToSubMgrService(toCoreNotification.getToSubscriptionMgrMsg(), callback);
}
if (statsEnabled) {
stats.log(toCoreNotification);
@ -458,6 +476,18 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
localSubscriptionService.onSubscriptionUpdate(msg.getSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getSubUpdate()), callback);
} else if (msg.hasAlarmSubUpdate()) {
localSubscriptionService.onSubscriptionUpdate(msg.getAlarmSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getAlarmSubUpdate()), callback);
} else if (msg.hasNotificationsSubUpdate()) {
TransportProtos.NotificationsSubscriptionUpdateProto subUpdateProto = msg.getNotificationsSubUpdate();
NotificationsSubscriptionUpdate notificationsSubscriptionUpdate;
if (StringUtils.isNotEmpty(subUpdateProto.getNotificationUpdate())) {
NotificationUpdate notificationUpdate = JacksonUtil.fromString(subUpdateProto.getNotificationUpdate(), NotificationUpdate.class);
notificationsSubscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate);
} else {
NotificationRequestUpdate notificationRequestUpdate = JacksonUtil.fromString(subUpdateProto.getNotificationRequestUpdate(), NotificationRequestUpdate.class);
notificationsSubscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate);
}
localSubscriptionService.onSubscriptionUpdate(subUpdateProto.getSessionId(),
subUpdateProto.getSubscriptionId(), notificationsSubscriptionUpdate, callback);
} else {
throwNotHandled(msg, callback);
}
@ -470,6 +500,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getTelemetrySub()), callback);
} else if (msg.hasAlarmSub()) {
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getAlarmSub()), callback);
} else if (msg.hasNotificationsSub()) {
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getNotificationsSub()), callback);
} else if (msg.hasNotificationsCountSub()) {
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getNotificationsCountSub()), callback);
} else if (msg.hasSubClose()) {
TbSubscriptionCloseProto closeProto = msg.getSubClose();
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback);
@ -509,6 +543,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
} else if (msg.hasNotificationUpdate()) {
TransportProtos.NotificationUpdateProto updateProto = msg.getNotificationUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB()));
UserId recipientId = new UserId(new UUID(updateProto.getRecipientIdMSB(), updateProto.getRecipientIdLSB()));
NotificationUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationUpdate.class);
subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, callback);
} else if (msg.hasNotificationRequestUpdate()) {
TransportProtos.NotificationRequestUpdateProto updateProto = msg.getNotificationRequestUpdate();
TenantId tenantId = TenantId.fromUUID(new UUID(updateProto.getTenantIdMSB(), updateProto.getTenantIdLSB()));
NotificationRequestUpdate update = JacksonUtil.fromString(updateProto.getUpdate(), NotificationRequestUpdate.class);
subscriptionManagerService.onNotificationRequestUpdate(tenantId, update, callback);
} else {
throwNotHandled(msg, callback);
}
@ -538,6 +583,17 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
}
}
private void forwardToNotificationSchedulerService(TransportProtos.NotificationSchedulerServiceMsg msg, TbCallback callback) {
TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
NotificationRequestId notificationRequestId = new NotificationRequestId(new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()));
try {
notificationSchedulerService.scheduleNotificationRequest(tenantId, notificationRequestId, msg.getTs());
callback.onSuccess();
} catch (Exception e) {
callback.onFailure(new RuntimeException("Failed to scheduler notification request", e));
}
}
private void forwardToEdgeNotificationService(EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
if (statsEnabled) {
stats.log(edgeNotificationMsg);

6
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.queue;
import com.google.protobuf.ProtocolStringList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -126,8 +127,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbAssetProfileCache assetProfileCache,
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
PartitionService partitionService, ApplicationEventPublisher eventPublisher,
TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;

7
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.EntityType;
@ -75,6 +76,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final TbAssetProfileCache assetProfileCache;
protected final TbApiUsageStateService apiUsageStateService;
protected final PartitionService partitionService;
protected final ApplicationEventPublisher eventPublisher;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService;
@ -83,7 +85,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
PartitionService partitionService, ApplicationEventPublisher eventPublisher,
TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
this.actorContext = actorContext;
this.encodingService = encodingService;
this.tenantProfileCache = tenantProfileCache;
@ -91,6 +94,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
this.assetProfileCache = assetProfileCache;
this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService;
this.eventPublisher = eventPublisher;
this.nfConsumer = nfConsumer;
this.jwtSettingsService = jwtSettingsService;
}
@ -205,6 +209,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
apiUsageStateService.onCustomerDelete((CustomerId) componentLifecycleMsg.getEntityId());
}
}
eventPublisher.publishEvent(componentLifecycleMsg);
}
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
actorContext.tellWithHighPriority(actorMsg);

2
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -79,7 +79,7 @@ import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity;
import org.thingsboard.server.exception.ToErrorResponseEntity;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;

8
application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java

@ -16,10 +16,10 @@
package org.thingsboard.server.service.security;
import com.google.common.util.concurrent.FutureCallback;
import org.thingsboard.server.service.telemetry.exception.AccessDeniedException;
import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException;
import org.thingsboard.server.service.telemetry.exception.InternalErrorException;
import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
import org.thingsboard.server.exception.AccessDeniedException;
import org.thingsboard.server.exception.EntityNotFoundException;
import org.thingsboard.server.exception.InternalErrorException;
import org.thingsboard.server.exception.UnauthorizedException;
/**
* Created by ashvayka on 31.03.18.

6
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -43,7 +43,11 @@ public enum Resource {
EDGE(EntityType.EDGE),
RPC(EntityType.RPC),
QUEUE(EntityType.QUEUE),
VERSION_CONTROL;
VERSION_CONTROL,
NOTIFICATION_TARGET(EntityType.NOTIFICATION_TARGET),
NOTIFICATION_TEMPLATE(EntityType.NOTIFICATION_TEMPLATE),
NOTIFICATION_REQUEST(EntityType.NOTIFICATION_REQUEST),
NOTIFICATION_RULE(EntityType.NOTIFICATION_RULE);
private final EntityType entityType;

3
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -40,6 +40,9 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
put(Resource.TB_RESOURCE, systemEntityPermissionChecker);
put(Resource.QUEUE, systemEntityPermissionChecker);
put(Resource.NOTIFICATION_TARGET, systemEntityPermissionChecker);
put(Resource.NOTIFICATION_TEMPLATE, systemEntityPermissionChecker);
put(Resource.NOTIFICATION_REQUEST, systemEntityPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

4
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -49,6 +49,10 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.RPC, tenantEntityPermissionChecker);
put(Resource.QUEUE, queuePermissionChecker);
put(Resource.VERSION_CONTROL, PermissionChecker.allowAllPermissionChecker);
put(Resource.NOTIFICATION_TARGET, tenantEntityPermissionChecker);
put(Resource.NOTIFICATION_TEMPLATE, tenantEntityPermissionChecker);
put(Resource.NOTIFICATION_REQUEST, tenantEntityPermissionChecker);
put(Resource.NOTIFICATION_RULE, tenantEntityPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

153
application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java

@ -0,0 +1,153 @@
/**
* Copyright © 2016-2022 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.slack;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiRequest;
import com.slack.api.methods.SlackApiTextResponse;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.request.conversations.ConversationsListRequest;
import com.slack.api.methods.request.users.UsersListRequest;
import com.slack.api.methods.response.conversations.ConversationsListResponse;
import com.slack.api.methods.response.users.UsersListResponse;
import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DefaultSlackService implements SlackService {
private final NotificationSettingsService notificationSettingsService;
private final Slack slack = Slack.getInstance();
private final Cache<String, List<SlackConversation>> cache = Caffeine.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.maximumSize(100)
.build();
private static final int CONVERSATIONS_LIMIT = 1000;
@Override
public void sendMessage(TenantId tenantId, String token, String conversationId, String message) {
ChatPostMessageRequest request = ChatPostMessageRequest.builder()
.channel(conversationId)
.text(message)
.build();
sendRequest(token, request, MethodsClient::chatPostMessage);
}
@Override
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) {
return cache.get(conversationType + ":" + token, k -> {
if (conversationType == SlackConversation.Type.USER) {
UsersListRequest request = UsersListRequest.builder()
.limit(CONVERSATIONS_LIMIT)
.build();
UsersListResponse response = sendRequest(token, request, MethodsClient::usersList);
return response.getMembers().stream()
.filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot())
.map(user -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(user.getId());
conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName()));
return conversation;
})
.collect(Collectors.toList());
} else {
ConversationsListRequest request = ConversationsListRequest.builder()
.types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL))
.limit(CONVERSATIONS_LIMIT)
.excludeArchived(true)
.build();
ConversationsListResponse response = sendRequest(token, request, MethodsClient::conversationsList);
return response.getChannels().stream()
.filter(channel -> !channel.isArchived())
.map(channel -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId());
conversation.setName("#" + channel.getName());
return conversation;
})
.collect(Collectors.toList());
}
});
}
@Override
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern))
.findFirst().orElse(null);
}
@Override
public String getToken(TenantId tenantId) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
if (slackConfig != null) {
return slackConfig.getBotToken();
} else {
return null;
}
}
private <T extends SlackApiRequest, R extends SlackApiTextResponse> R sendRequest(String token, T request, ThrowingBiFunction<MethodsClient, T, R> method) {
MethodsClient client = slack.methods(token);
R response;
try {
response = method.apply(client, request);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
if (!response.isOk()) {
String error = response.getError();
if (error == null) {
error = "unknown error";
}
if (error.contains("missing_scope")) {
String neededScope = response.getNeeded();
throw new RuntimeException("Bot token scope '" + neededScope + "' is needed");
}
throw new RuntimeException("Failed to send message via Slack: " + error);
}
return response;
}
}

8
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -28,6 +28,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
@ -149,7 +150,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private final TbServiceInfoProvider serviceInfoProvider;
private final EntityQueryRepository entityQueryRepository;
private final DbTypeInfoComponent dbTypeInfoComponent;
@Autowired @Lazy
private TelemetrySubscriptionService tsSubService;
@Value("${state.defaultInactivityTimeoutInSec}")
@ -196,11 +197,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
this.dbTypeInfoComponent = dbTypeInfoComponent;
}
@Autowired
public void setTsSubService(TelemetrySubscriptionService tsSubService) {
this.tsSubService = tsSubService;
}
@PostConstruct
public void init() {
super.init();

88
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -15,8 +15,10 @@
*/
package org.thingsboard.server.service.subscription;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
@ -29,6 +31,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -41,6 +44,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.service.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmSubscriptionUpdateProto;
@ -59,8 +63,11 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -81,34 +88,19 @@ import java.util.function.Predicate;
@Slf4j
@TbCoreComponent
@Service
@RequiredArgsConstructor
public class DefaultSubscriptionManagerService extends TbApplicationEventListener<PartitionChangeEvent> implements SubscriptionManagerService {
@Autowired
private AttributesService attrService;
@Autowired
private TimeseriesService tsService;
@Autowired
private NotificationsTopicService notificationsTopicService;
@Autowired
private PartitionService partitionService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
@Autowired
private TbQueueProducerProvider producerProvider;
@Autowired
private TbLocalSubscriptionService localSubscriptionService;
@Autowired
private DeviceStateService deviceStateService;
@Autowired
private TbClusterService clusterService;
private final AttributesService attrService;
private final TimeseriesService tsService;
private final NotificationsTopicService notificationsTopicService;
private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueProducerProvider producerProvider;
private final TbLocalSubscriptionService localSubscriptionService;
private final DeviceStateService deviceStateService;
private final TbClusterService clusterService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>();
@ -305,6 +297,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm,
false
);
notificationRuleProcessingService.onAlarmCreatedOrUpdated(tenantId, alarm);
callback.onSuccess();
}
@ -322,6 +315,45 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm,
true
);
notificationRuleProcessingService.onAlarmDeleted(tenantId, alarm);
callback.onSuccess();
}
@Override
public void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback) {
Set<TbSubscription> subscriptions = subscriptionsByEntityId.get(recipientId);
if (subscriptions != null) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate);
subscriptions.stream()
.filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS
|| subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.forEach(subscription -> {
if (serviceId.equals(subscription.getServiceId())) {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(),
subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId());
ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate);
TbProtoQueueMsg<ToCoreNotificationMsg> queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto);
toCoreNotificationsProducer.send(tpi, queueMsg, null);
}
});
}
callback.onSuccess();
}
@Override
public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate);
subscriptionsByEntityId.entrySet().stream()
.filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER)
.flatMap(subEntry -> subEntry.getValue().stream()
.filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS
|| sub.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.filter(sub -> sub.getServiceId().equals(serviceId)))
.forEach(subscription -> {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
});
callback.onSuccess();
}

45
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -49,22 +49,21 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AggHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AggKey;
import org.thingsboard.server.service.telemetry.cmd.v2.AggTimeSeriesCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggTimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
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.EntityHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.GetTsCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -96,7 +95,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService wsService;
private WebSocketService wsService;
@Autowired
private EntityService entityService;
@ -167,7 +166,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) {
public void handleCmd(WebSocketSessionRef session, EntityDataCmd cmd) {
TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx != null) {
log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
@ -358,7 +357,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) {
public void handleCmd(WebSocketSessionRef session, EntityCountCmd cmd) {
TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
ctx = createSubCtx(session, cmd);
@ -378,7 +377,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
public void handleCmd(WebSocketSessionRef session, AlarmDataCmd cmd) {
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
@ -469,7 +468,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
private TbEntityDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
@ -480,7 +479,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
return ctx;
}
private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
private TbEntityCountSubCtx createSubCtx(WebSocketSessionRef sessionRef, EntityCountCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId());
@ -492,7 +491,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
private TbAlarmDataSubCtx createSubCtx(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription,

31
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -21,18 +21,19 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -152,7 +153,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value));
break;
}
subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update));
subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update));
}
callback.onSuccess();
}
@ -163,7 +164,17 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
TbSubscription subscription = subscriptionsBySessionId
.getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
if (subscription != null && subscription.getType() == TbSubscriptionType.ALARMS) {
subscriptionUpdateExecutor.submit(() -> subscription.getUpdateConsumer().accept(sessionId, update));
subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update));
}
callback.onSuccess();
}
@Override
public void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback) {
TbSubscription subscription = subscriptionsBySessionId.getOrDefault(sessionId, Collections.emptyMap()).get(subscriptionId);
if (subscription != null && (subscription.getType() == TbSubscriptionType.NOTIFICATIONS
|| subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)) {
subscriptionUpdateExecutor.submit(() -> subscription.getUpdateProcessor().accept(subscription, update));
}
callback.onSuccess();
}

2
application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java

@ -17,7 +17,7 @@ package org.thingsboard.server.service.subscription;
import lombok.Data;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.service.telemetry.cmd.v2.AggKey;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AggKey;
@Data
public class ReadTsKvQueryInfo {

2
application/src/main/java/org/thingsboard/server/service/telemetry/sub/SubscriptionErrorCode.java → application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionErrorCode.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.sub;
package org.thingsboard.server.service.subscription;
public enum SubscriptionErrorCode {

6
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -19,10 +19,13 @@ import org.springframework.context.ApplicationListener;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import java.util.List;
@ -46,5 +49,8 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate, TbCallback callback);
void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback);
}

14
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -29,9 +29,9 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Arrays;
@ -50,10 +50,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
@Getter
protected PageData<EntityData> data;
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
public TbAbstractDataSubCtx(String serviceId, WebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
WebSocketSessionRef sessionRef, int cmdId) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.subToEntityIdMap = new ConcurrentHashMap<>();
}
@ -185,7 +185,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityData.getEntityId())
.updateConsumer((s, subscriptionUpdate) -> sendWsMsg(s, subscriptionUpdate, keysType))
.updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, keysType))
.allKeys(false)
.keyStates(keyStates)
.scope(scope)
@ -219,7 +219,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityData.getEntityId())
.updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues))
.updateProcessor((sub, subscriptionUpdate) -> sendWsMsg(sub.getSessionId(), subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues))
.allKeys(false)
.keyStates(keyStates)
.latestValues(latestValues)

19
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
@ -39,10 +38,10 @@ import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.HashMap;
@ -64,11 +63,11 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected final Lock wsLock = new ReentrantLock(true);
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
private final TelemetryWebSocketService wsService;
private final WebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final WebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
@ -80,10 +79,10 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected volatile ScheduledFuture<?> refreshTask;
protected volatile boolean stopped;
public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
public TbAbstractSubCtx(String serviceId, WebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
WebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
@ -142,7 +141,7 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.updateProcessor((subscription, subscriptionUpdate) -> dynamicValueSubUpdate(subscription.getSessionId(), subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)

16
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -39,11 +39,11 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Collection;
@ -82,10 +82,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private int alarmInvocationAttempts;
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
public TbAlarmDataSubCtx(String serviceId, WebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService,
TelemetryWebSocketSessionRef sessionRef, int cmdId,
WebSocketSessionRef sessionRef, int cmdId,
int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription;
@ -176,7 +176,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityData.getEntityId())
.updateConsumer(this::sendWsMsg)
.updateProcessor((sub, update) -> sendWsMsg(sub.getSessionId(), update))
.ts(startTs)
.build();
localSubscriptionService.addSubscription(subscription);

9
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmsSubscription.java

@ -17,13 +17,10 @@ package org.thingsboard.server.service.subscription;
import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import java.util.List;
import java.util.function.BiConsumer;
public class TbAlarmsSubscription extends TbSubscription<AlarmSubscriptionUpdate> {
@ -33,8 +30,8 @@ public class TbAlarmsSubscription extends TbSubscription<AlarmSubscriptionUpdate
@Builder
public TbAlarmsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<String, AlarmSubscriptionUpdate> updateConsumer, long ts) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateConsumer);
BiConsumer<TbSubscription<AlarmSubscriptionUpdate>, AlarmSubscriptionUpdate> updateProcessor, long ts) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateProcessor);
this.ts = ts;
}

6
application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscription.java

@ -19,7 +19,7 @@ import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.Map;
import java.util.function.BiConsumer;
@ -32,9 +32,9 @@ public class TbAttributeSubscription extends TbSubscription<TelemetrySubscriptio
@Builder
public TbAttributeSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<String, TelemetrySubscriptionUpdate> updateConsumer,
BiConsumer<TbSubscription<TelemetrySubscriptionUpdate>, TelemetrySubscriptionUpdate> updateProcessor,
boolean allKeys, Map<String, Long> keyStates, TbAttributeSubscriptionScope scope) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateConsumer);
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ATTRIBUTES, updateProcessor);
this.allKeys = allKeys;
this.keyStates = keyStates;
this.scope = scope;

10
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java

@ -19,18 +19,18 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
@Slf4j
public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
private volatile int result;
public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
public TbEntityCountSubCtx(String serviceId, WebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
}

18
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -28,13 +28,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
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;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Collections;
@ -59,9 +59,9 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
private final int maxEntitiesPerDataSubscription;
private Map<EntityId, Map<String, TsValue>> latestTsEntityData;
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
public TbEntityDataSubCtx(String serviceId, WebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) {
SubscriptionServiceStatistics stats, WebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription;
}

17
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java

@ -15,20 +15,19 @@
*/
package org.thingsboard.server.service.subscription;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
public interface TbEntityDataSubscriptionService {
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
void handleCmd(WebSocketSessionRef sessionId, EntityDataCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
void handleCmd(WebSocketSessionRef sessionId, EntityCountCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
void handleCmd(WebSocketSessionRef sessionId, AlarmDataCmd cmd);
void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);

7
application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java

@ -18,8 +18,9 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
public interface TbLocalSubscriptionService {
@ -33,6 +34,8 @@ public interface TbLocalSubscriptionService {
void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback);
void onSubscriptionUpdate(String sessionId, int subscriptionId, NotificationsSubscriptionUpdate update, TbCallback callback);
void onApplicationEvent(PartitionChangeEvent event);
void onApplicationEvent(ClusterTopologyChangeEvent event);

2
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java

@ -33,7 +33,7 @@ public abstract class TbSubscription<T> {
private final TenantId tenantId;
private final EntityId entityId;
private final TbSubscriptionType type;
private final BiConsumer<String, T> updateConsumer;
private final BiConsumer<? extends TbSubscription<T>, T> updateProcessor;
@Override
public boolean equals(Object o) {

2
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionType.java

@ -16,5 +16,5 @@
package org.thingsboard.server.service.subscription;
public enum TbSubscriptionType {
TIMESERIES, ATTRIBUTES, ALARMS
TIMESERIES, ATTRIBUTES, ALARMS, NOTIFICATIONS, NOTIFICATIONS_COUNT
}

92
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -49,10 +50,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeletePr
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.HashMap;
@ -105,6 +111,17 @@ public class TbSubscriptionUtils {
.setTs(alarmSub.getTs());
msgBuilder.setAlarmSub(alarmSubProto.build());
break;
case NOTIFICATIONS:
NotificationsSubscription notificationsSub = (NotificationsSubscription) subscription;
msgBuilder.setNotificationsSub(TransportProtos.NotificationsSubscriptionProto.newBuilder()
.setSub(subscriptionProto)
.setLimit(notificationsSub.getLimit()));
break;
case NOTIFICATIONS_COUNT:
NotificationsCountSubscription notificationsCountSub = (NotificationsCountSubscription) subscription;
msgBuilder.setNotificationsCountSub(TransportProtos.NotificationsCountSubscriptionProto.newBuilder()
.setSub(subscriptionProto));
break;
}
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
@ -166,6 +183,29 @@ public class TbSubscriptionUtils {
return builder.build();
}
public static NotificationsSubscription fromProto(TransportProtos.NotificationsSubscriptionProto notificationsSub) {
TbSubscriptionProto sub = notificationsSub.getSub();
return NotificationsSubscription.builder()
.serviceId(sub.getServiceId())
.sessionId(sub.getSessionId())
.subscriptionId(sub.getSubscriptionId())
.tenantId(TenantId.fromUUID(new UUID(sub.getTenantIdMSB(), sub.getTenantIdLSB())))
.entityId(EntityIdFactory.getByTypeAndUuid(sub.getEntityType(), new UUID(sub.getEntityIdMSB(), sub.getEntityIdLSB())))
.limit(notificationsSub.getLimit())
.build();
}
public static NotificationsCountSubscription fromProto(TransportProtos.NotificationsCountSubscriptionProto notificationsCountSub) {
TbSubscriptionProto sub = notificationsCountSub.getSub();
return NotificationsCountSubscription.builder()
.serviceId(sub.getServiceId())
.sessionId(sub.getSessionId())
.subscriptionId(sub.getSubscriptionId())
.tenantId(TenantId.fromUUID(new UUID(sub.getTenantIdMSB(), sub.getTenantIdLSB())))
.entityId(EntityIdFactory.getByTypeAndUuid(sub.getEntityType(), new UUID(sub.getEntityIdMSB(), sub.getEntityIdLSB())))
.build();
}
public static TelemetrySubscriptionUpdate fromProto(TbSubscriptionUpdateProto proto) {
if (proto.getErrorCode() > 0) {
return new TelemetrySubscriptionUpdate(proto.getSubscriptionId(), SubscriptionErrorCode.forCode(proto.getErrorCode()), proto.getErrorMsg());
@ -341,4 +381,50 @@ public class TbSubscriptionUtils {
msgBuilder.setAlarmDelete(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreNotificationMsg notificationsSubUpdateToProto(TbSubscription subscription, NotificationsSubscriptionUpdate update) {
TransportProtos.NotificationsSubscriptionUpdateProto.Builder updateProto = TransportProtos.NotificationsSubscriptionUpdateProto.newBuilder()
.setSessionId(subscription.getSessionId())
.setSubscriptionId(subscription.getSubscriptionId());
if (update.getNotificationUpdate() != null) {
updateProto.setNotificationUpdate(JacksonUtil.toString(update.getNotificationUpdate()));
}
if (update.getNotificationRequestUpdate() != null) {
updateProto.setNotificationRequestUpdate(JacksonUtil.toString(update.getNotificationRequestUpdate()));
}
return ToCoreNotificationMsg.newBuilder()
.setToLocalSubscriptionServiceMsg(TransportProtos.LocalSubscriptionServiceMsgProto.newBuilder()
.setNotificationsSubUpdate(updateProto)
.build())
.build();
}
public static ToCoreMsg notificationUpdateToProto(TenantId tenantId, UserId recipientId, NotificationUpdate notificationUpdate) {
TransportProtos.NotificationUpdateProto updateProto = TransportProtos.NotificationUpdateProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setRecipientIdMSB(recipientId.getId().getMostSignificantBits())
.setRecipientIdLSB(recipientId.getId().getLeastSignificantBits())
.setUpdate(JacksonUtil.toString(notificationUpdate))
.build();
return ToCoreMsg.newBuilder()
.setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder()
.setNotificationUpdate(updateProto)
.build())
.build();
}
public static ToCoreNotificationMsg notificationRequestUpdateToProto(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate) {
TransportProtos.NotificationRequestUpdateProto updateProto = TransportProtos.NotificationRequestUpdateProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setUpdate(JacksonUtil.toString(notificationRequestUpdate))
.build();
return ToCoreNotificationMsg.newBuilder()
.setToSubscriptionMgrMsg(SubscriptionMgrMsgProto.newBuilder()
.setNotificationRequestUpdate(updateProto)
.build())
.build();
}
}

6
application/src/main/java/org/thingsboard/server/service/subscription/TbTimeseriesSubscription.java

@ -19,7 +19,7 @@ import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.Map;
import java.util.function.BiConsumer;
@ -39,9 +39,9 @@ public class TbTimeseriesSubscription extends TbSubscription<TelemetrySubscripti
@Builder
public TbTimeseriesSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<String, TelemetrySubscriptionUpdate> updateConsumer,
BiConsumer<TbSubscription<TelemetrySubscriptionUpdate>, TelemetrySubscriptionUpdate> updateProcessor,
boolean allKeys, Map<String, Long> keyStates, long startTime, long endTime, boolean latestValues) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateConsumer);
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.TIMESERIES, updateProcessor);
this.allKeys = allKeys;
this.keyStates = keyStates;
this.startTime = startTime;

2
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.ThrowingRunnable;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.dao.exception.DataValidationException;

2
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.sync.ThrowingRunnable;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;

2
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.ThrowingRunnable;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;

48
application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java

@ -20,13 +20,17 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import javax.annotation.Nullable;
@ -38,33 +42,26 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Created by ashvayka on 27.03.18.
*/
@Slf4j
public abstract class AbstractSubscriptionService extends TbApplicationEventListener<PartitionChangeEvent>{
public abstract class AbstractSubscriptionService extends TbApplicationEventListener<PartitionChangeEvent> {
protected final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet();
protected final TbClusterService clusterService;
protected final PartitionService partitionService;
@Autowired
protected TbClusterService clusterService;
@Autowired
protected PartitionService partitionService;
@Autowired
protected Optional<SubscriptionManagerService> subscriptionManagerService;
protected ExecutorService wsCallBackExecutor;
public AbstractSubscriptionService(TbClusterService clusterService,
PartitionService partitionService) {
this.clusterService = clusterService;
this.partitionService = partitionService;
}
@Autowired(required = false)
public void setSubscriptionManagerService(Optional<SubscriptionManagerService> subscriptionManagerService) {
this.subscriptionManagerService = subscriptionManagerService;
}
abstract String getExecutorPrefix();
protected abstract String getExecutorPrefix();
@PostConstruct
public void initExecutor() {
@ -86,6 +83,22 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
}
}
protected void forwardToSubscriptionManagerService(TenantId tenantId, EntityId entityId,
Consumer<SubscriptionManagerService> toSubscriptionManagerService,
Supplier<TransportProtos.ToCoreMsg> toCore) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
toSubscriptionManagerService.accept(subscriptionManagerService.get());
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = toCore.get();
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
protected <T> void addWsCallback(ListenableFuture<T> saveFuture, Consumer<T> callback) {
Futures.addCallback(saveFuture, new FutureCallback<T>() {
@Override
@ -98,4 +111,5 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
}
}, wsCallBackExecutor);
}
}

57
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -19,10 +19,12 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -37,16 +39,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
@ -58,30 +56,15 @@ import java.util.Optional;
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService;
private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
public DefaultAlarmSubscriptionService(TbClusterService clusterService,
PartitionService partitionService,
AlarmService alarmService,
TbApiUsageReportClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) {
super(clusterService, partitionService);
this.alarmService = alarmService;
this.apiUsageClient = apiUsageClient;
this.apiUsageStateService = apiUsageStateService;
}
@Autowired(required = false)
public void setSubscriptionManagerService(Optional<SubscriptionManagerService> subscriptionManagerService) {
this.subscriptionManagerService = subscriptionManagerService;
}
@Override
String getExecutorPrefix() {
protected String getExecutorPrefix() {
return "alarm";
}
@ -169,17 +152,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
});
}
});
}
@ -189,17 +166,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
Alarm alarm = result.getAlarm();
TenantId tenantId = result.getAlarm().getTenantId();
for (EntityId entityId : result.getPropagatedEntitiesList()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
});
}
});
}

93
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -40,13 +40,11 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
@ -85,11 +83,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
public DefaultTelemetrySubscriptionService(AttributesService attrService,
TimeseriesService tsService,
@Lazy TbEntityViewService tbEntityViewService,
TbClusterService clusterService,
PartitionService partitionService,
TbApiUsageReportClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) {
super(clusterService, partitionService);
this.attrService = attrService;
this.tsService = tsService;
this.tbEntityViewService = tbEntityViewService;
@ -375,73 +370,49 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toAttributesUpdateProto(tenantId, entityId, scope, attributes);
});
}
private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice);
});
}
private void onTimeSeriesUpdate(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, ts, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toTimeseriesUpdateProto(tenantId, entityId, ts);
});
}
private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, List<TsKvLatestRemovingResult> ts) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
List<TsKvEntry> updated = new ArrayList<>();
List<String> deleted = new ArrayList<>();
ts.stream().filter(Objects::nonNull).forEach(res -> {
if (res.isRemoved()) {
if (res.getData() != null) {
updated.add(res.getData());
} else {
deleted.add(res.getKey());
}
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
List<TsKvEntry> updated = new ArrayList<>();
List<String> deleted = new ArrayList<>();
ts.stream().filter(Objects::nonNull).forEach(res -> {
if (res.isRemoved()) {
if (res.getData() != null) {
updated.add(res.getData());
} else {
deleted.add(res.getKey());
}
});
}
});
subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY);
subscriptionManagerService.get().onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
subscriptionManagerService.onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY);
subscriptionManagerService.onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY);
}, () -> {
return TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys);
});
}
private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) {

2
application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java

@ -41,6 +41,4 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService {
void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback);
}

71
application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java

@ -0,0 +1,71 @@
/**
* Copyright © 2016-2022 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.ttl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.dao.notification.NotificationRequestDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE_NAME;
@Service
@ConditionalOnExpression("${sql.ttl.notifications.enabled:true} && ${sql.ttl.notifications.ttl:0} > 0")
@Slf4j
public class NotificationsCleanUpService extends AbstractCleanUpService {
private final SqlPartitioningRepository partitioningRepository;
private final NotificationRequestDao notificationRequestDao;
@Value("${sql.ttl.notifications.ttl:2592000}")
private long ttlInSec;
@Value("${sql.notifications.partition_size:168}")
private int partitionSizeInHours;
public NotificationsCleanUpService(PartitionService partitionService, SqlPartitioningRepository partitioningRepository,
NotificationRequestDao notificationRequestDao) {
super(partitionService);
this.partitioningRepository = partitioningRepository;
this.notificationRequestDao = notificationRequestDao;
}
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.notifications.checking_interval_ms:86400000})}",
fixedDelayString = "${sql.ttl.notifications.checking_interval_ms:86400000}")
public void cleanUp() {
long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours);
if (!isSystemTenantPartitionMine()) {
partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs);
return;
}
long lastRemovedNotificationTs = partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs);
if (lastRemovedNotificationTs > 0) {
long gap = TimeUnit.MINUTES.toMillis(10);
long requestExpTime = lastRemovedNotificationTs - TimeUnit.SECONDS.toMillis(NotificationRequestConfig.MAX_SENDING_DELAY) - gap;
// TODO: double-check this
notificationRequestDao.removeAllByCreatedTimeBefore(requestExpTime);
}
}
}

290
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java → application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -22,8 +22,8 @@ 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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.TenantRateLimitException;
import org.thingsboard.server.exception.UnauthorizedException;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.AccessValidator;
@ -56,26 +57,28 @@ import org.thingsboard.server.service.security.ValidationResult;
import org.thingsboard.server.service.security.ValidationResultCode;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
import org.thingsboard.server.service.subscription.TbAttributeSubscription;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
import org.thingsboard.server.service.subscription.TbTimeseriesSubscription;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
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.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.SubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TelemetryPluginCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
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.UnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@ -95,6 +98,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -104,7 +108,8 @@ import java.util.stream.Collectors;
@Service
@TbCoreComponent
@Slf4j
public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService {
@RequiredArgsConstructor
public class DefaultWebSocketService implements WebSocketService {
public static final int NUMBER_OF_PING_ATTEMPTS = 3;
@ -120,50 +125,57 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private final ConcurrentMap<String, WsSessionMetaData> wsSessionsMap = new ConcurrentHashMap<>();
@Autowired
private TbLocalSubscriptionService oldSubService;
@Autowired
private TbEntityDataSubscriptionService entityDataSubService;
@Autowired
private TelemetryWebSocketMsgEndpoint msgEndpoint;
@Autowired
private AccessValidator accessValidator;
@Autowired
private AttributesService attributesService;
@Autowired
private TimeseriesService tsService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
@Autowired
private TbTenantProfileCache tenantProfileCache;
private final TbLocalSubscriptionService oldSubService;
private final TbEntityDataSubscriptionService entityDataSubService;
private final NotificationCommandsHandler notificationCmdsHandler;
private final WebSocketMsgEndpoint msgEndpoint;
private final AccessValidator accessValidator;
private final AttributesService attributesService;
private final TimeseriesService tsService;
private final TbServiceInfoProvider serviceInfoProvider;
private final TbTenantProfileCache tenantProfileCache;
@Value("${server.ws.ping_timeout:30000}")
private long pingTimeout;
private ConcurrentMap<TenantId, Set<String>> tenantSubscriptionsMap = new ConcurrentHashMap<>();
private ConcurrentMap<CustomerId, Set<String>> customerSubscriptionsMap = new ConcurrentHashMap<>();
private ConcurrentMap<UserId, Set<String>> regularUserSubscriptionsMap = new ConcurrentHashMap<>();
private ConcurrentMap<UserId, Set<String>> publicUserSubscriptionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<TenantId, Set<String>> tenantSubscriptionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<CustomerId, Set<String>> customerSubscriptionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<UserId, Set<String>> regularUserSubscriptionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<UserId, Set<String>> publicUserSubscriptionsMap = new ConcurrentHashMap<>();
private ExecutorService executor;
private ScheduledExecutorService pingExecutor;
private String serviceId;
private ScheduledExecutorService pingExecutor;
private List<WsCmdListHandler<TelemetryPluginCmdsWrapper, ?>> telemetryCmdsHandlers;
private List<WsCmdHandler<NotificationCmdsWrapper, ? extends WsCmd>> notificationCmdsHandlers;
@PostConstruct
public void initExecutor() {
public void init() {
serviceId = serviceInfoProvider.getServiceId();
executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass());
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::getEntityDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getAlarmDataUnsubscribeCmds, this::handleWsDataUnsubscribeCmd),
newCmdsHandler(TelemetryPluginCmdsWrapper::getEntityCountUnsubscribeCmds, this::handleWsDataUnsubscribeCmd)
);
notificationCmdsHandlers = List.of(
newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd),
newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd),
newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd),
newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd)
);
}
@PreDestroy
@ -178,7 +190,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
@Override
public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
public void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) {
String sessionId = sessionRef.getSessionId();
log.debug(PROCESSING_MSG, sessionId, event);
switch (event.getEventType()) {
@ -198,49 +210,19 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
@Override
public void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg) {
public void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg) {
if (log.isTraceEnabled()) {
log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg);
}
try {
TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class);
if (cmdsWrapper != null) {
if (cmdsWrapper.getAttrSubCmds() != null) {
cmdsWrapper.getAttrSubCmds().forEach(cmd -> {
if (processSubscription(sessionRef, cmd)) {
handleWsAttributesSubscriptionCmd(sessionRef, cmd);
}
});
}
if (cmdsWrapper.getTsSubCmds() != null) {
cmdsWrapper.getTsSubCmds().forEach(cmd -> {
if (processSubscription(sessionRef, cmd)) {
handleWsTimeseriesSubscriptionCmd(sessionRef, cmd);
}
});
}
if (cmdsWrapper.getHistoryCmds() != null) {
cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityDataCmds() != null) {
cmdsWrapper.getEntityDataCmds().forEach(cmd -> handleWsEntityDataCmd(sessionRef, cmd));
}
if (cmdsWrapper.getAlarmDataCmds() != null) {
cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountCmds() != null) {
cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) {
cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
switch (sessionRef.getSessionType()) {
case TELEMETRY:
processTelemetryCmds(sessionRef, msg);
break;
case NOTIFICATIONS:
processNotificationCmds(sessionRef, msg);
break;
}
} catch (IOException e) {
log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
@ -248,7 +230,34 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsEntityDataCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
private void processTelemetryCmds(WebSocketSessionRef sessionRef, String msg) throws JsonProcessingException {
TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class);
if (cmdsWrapper == null) {
return;
}
for (WsCmdListHandler<TelemetryPluginCmdsWrapper, ?> cmdHandler : telemetryCmdsHandlers) {
List<?> cmds = cmdHandler.extractCmds(cmdsWrapper);
if (cmds != null) {
cmdHandler.handle(sessionRef, cmds);
}
}
}
private void processNotificationCmds(WebSocketSessionRef sessionRef, String msg) throws IOException {
NotificationCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, NotificationCmdsWrapper.class);
for (WsCmdHandler<NotificationCmdsWrapper, ? extends WsCmd> cmdHandler : notificationCmdsHandlers) {
WsCmd cmd = cmdHandler.extractCmd(cmdsWrapper);
if (cmd != null) {
String sessionId = sessionRef.getSessionId();
if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) {
cmdHandler.handle(sessionRef, cmd); // todo: handle exceptions
}
}
}
}
private void handleWsEntityDataCmd(WebSocketSessionRef sessionRef, EntityDataCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -258,7 +267,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
private void handleWsEntityCountCmd(WebSocketSessionRef sessionRef, EntityCountCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -268,7 +277,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
private void handleWsAlarmDataCmd(WebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -278,7 +287,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsDataUnsubscribeCmd(TelemetryWebSocketSessionRef sessionRef, UnsubscribeCmd cmd) {
private void handleWsDataUnsubscribeCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -316,7 +325,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) {
private void processSessionClose(WebSocketSessionRef sessionRef) {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration != null) {
String sessionId = "[" + sessionRef.getSessionId() + "]";
@ -350,7 +359,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private boolean processSubscription(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
private boolean processSubscription(WebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) return true;
@ -422,7 +431,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private void handleWsAttributesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) {
private void handleWsAttributesSubscriptionCmd(WebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) {
if (!processSubscription(sessionRef, cmd)) {
return;
}
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -443,7 +456,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
private void handleWsAttributesSubscriptionByKeys(WebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId,
List<String> keys) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<>() {
@ -467,7 +480,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
.allKeys(false)
.keyStates(subState)
.scope(scope)
.updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg)
.updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update))
.build();
oldSubService.addSubscription(sub);
}
@ -494,7 +507,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsHistoryCmd(TelemetryWebSocketSessionRef sessionRef, GetHistoryCmd cmd) {
private void handleWsHistoryCmd(WebSocketSessionRef sessionRef, GetHistoryCmd cmd) {
String sessionId = sessionRef.getSessionId();
WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
if (sessionMD == null) {
@ -544,7 +557,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
on(r -> Futures.addCallback(tsService.findAll(sessionRef.getSecurityCtx().getTenantId(), entityId, queries), callback, executor), callback::onFailure));
}
private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef,
private void handleWsAttributesSubscription(WebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<>() {
@Override
@ -565,7 +578,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
.entityId(entityId)
.allKeys(true)
.keyStates(subState)
.updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg)
.updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update))
.scope(scope).build();
oldSubService.addSubscription(sub);
}
@ -587,7 +600,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsTimeseriesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) {
private void handleWsTimeseriesSubscriptionCmd(WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) {
if (!processSubscription(sessionRef, cmd)) {
return;
}
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -607,7 +624,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsTimeseriesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
private void handleWsTimeseriesSubscriptionByKeys(WebSocketSessionRef sessionRef,
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
long startTs;
if (cmd.getTimeWindow() > 0) {
@ -631,7 +648,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsTimeseriesSubscription(TelemetryWebSocketSessionRef sessionRef,
private void handleWsTimeseriesSubscription(WebSocketSessionRef sessionRef,
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@Override
@ -646,7 +663,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
.subscriptionId(cmd.getCmdId())
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg)
.updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update))
.allKeys(true)
.keyStates(subState).build();
oldSubService.addSubscription(sub);
@ -669,7 +686,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure));
}
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) {
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) {
return new FutureCallback<>() {
@Override
public void onSuccess(List<TsKvEntry> data) {
@ -684,7 +701,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
.subscriptionId(cmd.getCmdId())
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg)
.updateProcessor((subscription, update) -> sendWsMsg(subscription.getSessionId(), update))
.allKeys(false)
.keyStates(subState).build();
oldSubService.addSubscription(sub);
@ -704,7 +721,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
};
}
private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
private void unsubscribe(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
oldSubService.cancelAllSessionSubscriptions(sessionId);
} else {
@ -712,7 +729,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
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!");
@ -727,7 +744,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
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!");
@ -741,7 +758,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
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!");
@ -756,7 +773,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
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!");
@ -766,11 +783,11 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
private boolean validateSessionMetadata(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
return validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId);
}
private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, int cmdId, String sessionId) {
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);
@ -783,15 +800,15 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, EntityDataUpdate update) {
private void sendWsMsg(WebSocketSessionRef sessionRef, EntityDataUpdate update) {
sendWsMsg(sessionRef, update.getCmdId(), update);
}
private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) {
private void sendWsMsg(WebSocketSessionRef sessionRef, TelemetrySubscriptionUpdate update) {
sendWsMsg(sessionRef, update.getSubscriptionId(), update);
}
private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, int cmdId, Object update) {
private void sendWsMsg(WebSocketSessionRef sessionRef, int cmdId, Object update) {
try {
String msg = jsonMapper.writeValueAsString(update);
executor.submit(() -> {
@ -935,9 +952,52 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return limit == 0 ? DEFAULT_LIMIT : limit;
}
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(TelemetryWebSocketSessionRef sessionRef) {
private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) {
return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()))
.map(TenantProfile::getDefaultProfileConfiguration).orElse(null);
}
public static <W, C> WsCmdHandler<W, C> newCmdHandler(java.util.function.Function<W, C> cmdExtractor,
BiConsumer<WebSocketSessionRef, C> handler) {
return new WsCmdHandler<>(cmdExtractor, handler);
}
public static <W, C> WsCmdListHandler<W, C> newCmdsHandler(java.util.function.Function<W, List<C>> cmdsExtractor,
BiConsumer<WebSocketSessionRef, C> handler) {
return new WsCmdListHandler<>(cmdsExtractor, handler);
}
@RequiredArgsConstructor
public static class WsCmdHandler<W, C> {
private final java.util.function.Function<W, C> cmdExtractor;
private final BiConsumer<WebSocketSessionRef, C> handler;
public C extractCmd(W cmdsWrapper) {
return cmdExtractor.apply(cmdsWrapper);
}
@SuppressWarnings("unchecked")
public void handle(WebSocketSessionRef sessionRef, Object cmd) {
handler.accept(sessionRef, (C) cmd);
}
}
@RequiredArgsConstructor
public static class WsCmdListHandler<W, C> {
private final java.util.function.Function<W, List<C>> cmdsExtractor;
private final BiConsumer<WebSocketSessionRef, C> handler;
public List<C> extractCmds(W cmdsWrapper) {
return cmdsExtractor.apply(cmdsWrapper);
}
@SuppressWarnings("unchecked")
public void handle(WebSocketSessionRef sessionRef, List<?> cmds) {
cmds.forEach(cmd -> {
handler.accept(sessionRef, (C) cmd);
});
}
}
}

2
application/src/main/java/org/thingsboard/server/service/telemetry/SessionEvent.java → application/src/main/java/org/thingsboard/server/service/ws/SessionEvent.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
import lombok.Getter;
import lombok.ToString;

11
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java → application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java

@ -13,20 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import java.io.IOException;
/**
* Created by ashvayka on 27.03.18.
*/
public interface TelemetryWebSocketMsgEndpoint {
public interface WebSocketMsgEndpoint {
void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException;
void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException;
void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException;
void sendPing(WebSocketSessionRef sessionRef, long currentTime) throws IOException;
void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException;
void close(WebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException;
}

15
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java → application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java

@ -13,21 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import org.thingsboard.server.service.ws.SessionEvent;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
/**
* Created by ashvayka on 27.03.18.
*/
public interface TelemetryWebSocketService {
public interface WebSocketService {
void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent sessionEvent);
void handleWebSocketSessionEvent(WebSocketSessionRef sessionRef, SessionEvent sessionEvent);
void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg);
void handleWebSocketMsg(WebSocketSessionRef sessionRef, String msg);
void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);

30
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java → application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.net.InetSocketAddress;
@ -25,34 +27,25 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by ashvayka on 27.03.18.
*/
public class TelemetryWebSocketSessionRef {
@RequiredArgsConstructor
@Builder
@Getter
public class WebSocketSessionRef {
private static final long serialVersionUID = 1L;
@Getter
private final String sessionId;
@Getter
private final SecurityUser securityCtx;
@Getter
private final InetSocketAddress localAddress;
@Getter
private final InetSocketAddress remoteAddress;
@Getter
private final AtomicInteger sessionSubIdSeq;
public TelemetryWebSocketSessionRef(String sessionId, SecurityUser securityCtx, InetSocketAddress localAddress, InetSocketAddress remoteAddress) {
this.sessionId = sessionId;
this.securityCtx = securityCtx;
this.localAddress = localAddress;
this.remoteAddress = remoteAddress;
this.sessionSubIdSeq = new AtomicInteger();
}
private final WebSocketSessionType sessionType;
private final AtomicInteger sessionSubIdSeq = new AtomicInteger();
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TelemetryWebSocketSessionRef that = (TelemetryWebSocketSessionRef) o;
WebSocketSessionRef that = (WebSocketSessionRef) o;
return Objects.equals(sessionId, that.sessionId);
}
@ -63,10 +56,11 @@ public class TelemetryWebSocketSessionRef {
@Override
public String toString() {
return "TelemetryWebSocketSessionRef{" +
return "WebSocketSessionRef{" +
"sessionId='" + sessionId + '\'' +
", localAddress=" + localAddress +
", remoteAddress=" + remoteAddress +
", sessionType=" + sessionType +
'}';
}
}

38
application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionType.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2022 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.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Optional;
@RequiredArgsConstructor
@Getter
public enum WebSocketSessionType {
TELEMETRY("telemetry"),
NOTIFICATIONS("notifications");
private final String name;
public static Optional<WebSocketSessionType> forName(String name) {
return Arrays.stream(values())
.filter(sessionType -> sessionType.getName().equals(name))
.findFirst();
}
}

10
application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java → application/src/main/java/org/thingsboard/server/service/ws/WsSessionMetaData.java

@ -13,27 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws;
/**
* Created by ashvayka on 27.03.18.
*/
public class WsSessionMetaData {
private TelemetryWebSocketSessionRef sessionRef;
private WebSocketSessionRef sessionRef;
private long lastActivityTime;
public WsSessionMetaData(TelemetryWebSocketSessionRef sessionRef) {
public WsSessionMetaData(WebSocketSessionRef sessionRef) {
super();
this.sessionRef = sessionRef;
this.lastActivityTime = System.currentTimeMillis();
}
public TelemetryWebSocketSessionRef getSessionRef() {
public WebSocketSessionRef getSessionRef() {
return sessionRef;
}
public void setSessionRef(TelemetryWebSocketSessionRef sessionRef) {
public void setSessionRef(WebSocketSessionRef sessionRef) {
this.sessionRef = sessionRef;
}

232
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -0,0 +1,232 @@
/**
* Copyright © 2016-2022 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.notification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
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.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsCountSubscription;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscription;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationCommandsHandler implements NotificationCommandsHandler {
private final NotificationService notificationService;
private final TbLocalSubscriptionService localSubscriptionService;
private final NotificationManager notificationManager;
private final TbServiceInfoProvider serviceInfoProvider;
@Autowired @Lazy
private WebSocketService wsService;
@Override
public void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd) {
log.debug("[{}] Handling unread notifications subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId());
SecurityUser securityCtx = sessionRef.getSecurityCtx();
NotificationsSubscription subscription = NotificationsSubscription.builder()
.serviceId(serviceInfoProvider.getServiceId())
.sessionId(sessionRef.getSessionId())
.subscriptionId(cmd.getCmdId())
.tenantId(securityCtx.getTenantId())
.entityId(securityCtx.getId())
.updateProcessor(this::handleNotificationsSubscriptionUpdate)
.limit(cmd.getLimit())
.build();
localSubscriptionService.addSubscription(subscription);
fetchUnreadNotifications(subscription);
sendUpdate(sessionRef.getSessionId(), subscription.createFullUpdate());
}
@Override
public void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd) {
log.debug("[{}] Handling unread notifications count subscription cmd (cmdId: {})", sessionRef.getSessionId(), cmd.getCmdId());
SecurityUser securityCtx = sessionRef.getSecurityCtx();
NotificationsCountSubscription subscription = NotificationsCountSubscription.builder()
.serviceId(serviceInfoProvider.getServiceId())
.sessionId(sessionRef.getSessionId())
.subscriptionId(cmd.getCmdId())
.tenantId(securityCtx.getTenantId())
.entityId(securityCtx.getId())
.updateProcessor(this::handleNotificationsCountSubscriptionUpdate)
.build();
localSubscriptionService.addSubscription(subscription);
fetchUnreadNotificationsCount(subscription);
sendUpdate(sessionRef.getSessionId(), subscription.createUpdate());
}
private void fetchUnreadNotifications(NotificationsSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByUserId(subscription.getTenantId(),
(UserId) subscription.getEntityId(), subscription.getLimit());
subscription.getLatestUnreadNotifications().clear();
notifications.getData().forEach(notification -> {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
});
subscription.getTotalUnreadCounter().set((int) notifications.getTotalElements());
}
private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId());
int unreadCount = notificationService.countUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId());
subscription.getUnreadCounter().set(unreadCount);
}
/* Notifications subscription update handling */
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
}
private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification();
if (update.isNew()) {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
subscription.getTotalUnreadCounter().incrementAndGet();
if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) {
Set<UUID> beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit())
.map(IdBased::getUuidId).collect(Collectors.toSet());
beyondLimit.forEach(notificationId -> subscription.getLatestUnreadNotifications().remove(notificationId));
}
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
} else {
if (notification.getStatus() != NotificationStatus.READ) {
if (subscription.getLatestUnreadNotifications().containsKey(notification.getUuidId())) {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
}
} else {
if (subscription.getLatestUnreadNotifications().containsKey(notification.getUuidId())) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} else {
subscription.getTotalUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createCountUpdate());
}
}
}
}
private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
NotificationRequestId notificationRequestId = update.getNotificationRequestId();
if (update.isDeleted()) {
if (subscription.getLatestUnreadNotifications().values().stream()
.anyMatch(notification -> notification.getRequestId().equals(notificationRequestId))) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
}
} else {
subscription.getLatestUnreadNotifications().values().stream()
.filter(notification -> notification.getRequestId().equals(notificationRequestId))
.forEach(notification -> {
notification.setInfo(update.getNotificationInfo());
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
});
}
}
/* Notifications count subscription update handling */
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
}
private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification();
if (update.isNew()) {
subscription.getUnreadCounter().incrementAndGet();
} else if (notification.getStatus() == NotificationStatus.READ) {
// for now this can only happen when user marks notification as read
subscription.getUnreadCounter().decrementAndGet();
}
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
if (update.isDeleted()) {
fetchUnreadNotificationsCount(subscription);
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
}
private void sendUpdate(String sessionId, CmdUpdate update) {
log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update);
wsService.sendWsMsg(sessionId, update);
}
@Override
public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) {
SecurityUser securityCtx = sessionRef.getSecurityCtx();
cmd.getNotifications().stream()
.map(NotificationId::new)
.forEach(notificationId -> {
notificationManager.markNotificationAsRead(securityCtx.getTenantId(), securityCtx.getId(), notificationId);
// fixme: should send bulk update event, not a separate event for each notification
});
}
@Override
public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId());
}
}

34
application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2022 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.notification;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd;
import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
public interface NotificationCommandsHandler {
void handleUnreadNotificationsSubCmd(WebSocketSessionRef sessionRef, NotificationsSubCmd cmd);
void handleUnreadNotificationsCountSubCmd(WebSocketSessionRef sessionRef, NotificationsCountSubCmd cmd);
void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd);
void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd);
}

31
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkNotificationsAsReadCmd.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MarkNotificationsAsReadCmd implements WsCmd {
private int cmdId;
private List<UUID> notifications;
}

31
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import lombok.Data;
@Data
public class NotificationCmdsWrapper {
private NotificationsCountSubCmd unreadCountSubCmd;
private NotificationsSubCmd unreadSubCmd;
private MarkNotificationsAsReadCmd markAsReadCmd;
private NotificationsUnsubCmd unsubCmd;
}

27
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsCountSubCmd.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationsCountSubCmd implements WsCmd {
private int cmdId;
}

28
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationsSubCmd implements WsCmd {
private int cmdId;
private int limit;
}

28
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsUnsubCmd.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NotificationsUnsubCmd implements UnsubscribeCmd, WsCmd {
private int cmdId;
}

46
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType;
@Getter
@ToString
public class UnreadNotificationsCountUpdate extends CmdUpdate {
private final int totalUnreadCount;
@Builder
@JsonCreator
public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg,
@JsonProperty("totalUnreadCount") int totalUnreadCount) {
super(cmdId, errorCode, errorMsg);
this.totalUnreadCount = totalUnreadCount;
}
@Override
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.NOTIFICATIONS_COUNT;
}
}

55
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType;
import java.util.Collection;
@Getter
@ToString(exclude = "notifications")
public class UnreadNotificationsUpdate extends CmdUpdate {
private final Collection<Notification> notifications;
private final Notification update;
private final int totalUnreadCount;
@Builder
@JsonCreator
public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg,
@JsonProperty("notifications") Collection<Notification> notifications,
@JsonProperty("update") Notification update,
@JsonProperty("totalUnreadCount") int totalUnreadCount) {
super(cmdId, errorCode, errorMsg);
this.notifications = notifications;
this.update = update;
this.totalUnreadCount = totalUnreadCount;
}
@Override
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.NOTIFICATIONS;
}
}

20
application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/WsCmd.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2022 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.notification.cmd;
public interface WsCmd {
int getCmdId();
}

34
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2022 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.notification.sub;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.notification.NotificationInfo;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NotificationRequestUpdate {
private NotificationRequestId notificationRequestId;
private String notificationReason;
private NotificationInfo notificationInfo;
private boolean deleted;
}

31
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 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.notification.sub;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.Notification;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NotificationUpdate {
private Notification notification;
private boolean isNew;
}

47
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2022 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.notification.sub;
import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@Getter
public class NotificationsCountSubscription extends TbSubscription<NotificationsSubscriptionUpdate> {
private final AtomicInteger unreadCounter = new AtomicInteger();
@Builder
public NotificationsCountSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<NotificationsCountSubscription, NotificationsSubscriptionUpdate> updateProcessor) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS_COUNT, updateProcessor);
}
public UnreadNotificationsCountUpdate createUpdate() {
return UnreadNotificationsCountUpdate.builder()
.cmdId(getSubscriptionId())
.totalUnreadCount(unreadCounter.get())
.build();
}
}

81
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java

@ -0,0 +1,81 @@
/**
* Copyright © 2016-2022 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.notification.sub;
import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@Getter
public class NotificationsSubscription extends TbSubscription<NotificationsSubscriptionUpdate> {
private final Map<UUID, Notification> latestUnreadNotifications = new HashMap<>();
private final int limit;
private final AtomicInteger totalUnreadCounter = new AtomicInteger();
@Builder
public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<NotificationsSubscription, NotificationsSubscriptionUpdate> updateProcessor,
int limit) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor);
this.limit = limit;
}
public UnreadNotificationsUpdate createFullUpdate() {
return UnreadNotificationsUpdate.builder()
.cmdId(getSubscriptionId())
.notifications(getSortedNotifications())
.totalUnreadCount(totalUnreadCounter.get())
.build();
}
public List<Notification> getSortedNotifications() {
return latestUnreadNotifications.values().stream()
.sorted(Comparator.comparing(BaseData::getCreatedTime, Comparator.reverseOrder()))
.collect(Collectors.toList());
}
public UnreadNotificationsUpdate createPartialUpdate(Notification notification) {
return UnreadNotificationsUpdate.builder()
.cmdId(getSubscriptionId())
.update(notification)
.totalUnreadCount(totalUnreadCounter.get())
.build();
}
public UnreadNotificationsUpdate createCountUpdate() {
return UnreadNotificationsUpdate.builder()
.cmdId(getSubscriptionId())
.totalUnreadCount(totalUnreadCounter.get())
.build();
}
}

36
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscriptionUpdate.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2022 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.notification.sub;
import lombok.Data;
@Data
public class NotificationsSubscriptionUpdate {
private final NotificationUpdate notificationUpdate;
private final NotificationRequestUpdate notificationRequestUpdate;
public NotificationsSubscriptionUpdate(NotificationUpdate notificationUpdate) {
this.notificationUpdate = notificationUpdate;
this.notificationRequestUpdate = null;
}
public NotificationsSubscriptionUpdate(NotificationRequestUpdate notificationRequestUpdate) {
this.notificationUpdate = null;
this.notificationRequestUpdate = notificationRequestUpdate;
}
}

2
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryFeature.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryFeature.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws.telemetry;
/**
* Created by ashvayka on 08.05.17.

5
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/TelemetryWebSocketTextMsg.java

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry;
package org.thingsboard.server.service.ws.telemetry;
import lombok.Data;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
/**
* Created by ashvayka on 27.03.18.
@ -23,7 +24,7 @@ import lombok.Data;
@Data
public class TelemetryWebSocketTextMsg {
private final TelemetryWebSocketSessionRef sessionRef;
private final WebSocketSessionRef sessionRef;
private final String payload;
}

20
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java

@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.cmd;
package org.thingsboard.server.service.ws.telemetry.cmd;
import lombok.Data;
import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
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.EntityCountUnsubscribeCmd;
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.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import java.util.List;

4
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/AttributesSubscriptionCmd.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/AttributesSubscriptionCmd.java

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.cmd.v1;
package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.NoArgsConstructor;
import org.thingsboard.server.service.telemetry.TelemetryFeature;
import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
/**
* @author Andrew Shvayka

2
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/GetHistoryCmd.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/GetHistoryCmd.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.cmd.v1;
package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.AllArgsConstructor;
import lombok.Data;

4
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v1/SubscriptionCmd.java → application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/v1/SubscriptionCmd.java

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.telemetry.cmd.v1;
package org.thingsboard.server.service.ws.telemetry.cmd.v1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.service.telemetry.TelemetryFeature;
import org.thingsboard.server.service.ws.telemetry.TelemetryFeature;
@NoArgsConstructor
@AllArgsConstructor

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save