Browse Source

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

pull/8212/head
YevhenBondarenko 4 years ago
parent
commit
535b411b7e
  1. 28
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  2. 1
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  3. 8
      application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
  4. 10
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  5. 53
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultApiLimitService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  8. 6
      application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java
  9. 50
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  10. 169
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java
  11. 14
      application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java
  12. 79
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  13. 42
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  14. 90
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  15. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java
  16. 62
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  17. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java
  18. 45
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  19. 27
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java
  20. 3
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  21. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  22. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  23. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java
  24. 4
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  25. 6
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  26. 5
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  27. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  28. 11
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  29. 3
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  30. 108
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  31. 2
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java
  32. 10
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java
  33. 3
      application/src/main/resources/thingsboard.yml
  34. 5
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  35. 3
      application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
  36. 8
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  37. 63
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  38. 36
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  39. 6
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java
  40. 13
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  41. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java
  42. 1
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  43. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/attributes/AbstractAttributesMqttV5Test.java
  44. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/connection/AbstractMqttV5ClientConnectionTest.java
  45. 3
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/subscribe/AbstractMqttV5ClientSubscriptionTest.java
  46. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/unsubscribe/AbstractMqttV5ClientUnsubscribeTest.java
  47. 3
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/AbstractMqttV5RpcTest.java
  48. 7
      application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/AbstractMqttV5ClientSparkplugAttributesTest.java
  49. 4
      application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/rpc/AbstractMqttV5RpcSparkplugTest.java
  50. 13
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java
  51. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java
  52. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java
  53. 25
      common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiLimitService.java
  54. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java
  55. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  56. 18
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java
  57. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java
  58. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java
  59. 13
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java
  60. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  61. 78
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  62. 34
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  63. 22
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java
  64. 14
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceInactivityNotificationInfo.java
  65. 28
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java
  66. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java
  67. 22
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  68. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java
  69. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java
  70. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java
  71. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  72. 37
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java
  73. 9
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java
  74. 12
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java
  75. 36
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java
  76. 28
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java
  77. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java
  78. 20
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java
  79. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java
  80. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilterType.java
  81. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java
  82. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java
  83. 20
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  84. 22
      common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
  85. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java
  86. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  87. 5
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  88. 2
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java
  89. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  90. 21
      dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java
  91. 3
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  92. 39
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java
  93. 6
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java
  94. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  95. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java
  96. 32
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCaffeineCache.java
  97. 35
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestRedisCache.java
  98. 19
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  99. 11
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  100. 13
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

28
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -89,6 +89,10 @@ CREATE TABLE IF NOT EXISTS alarm_comment (
) PARTITION BY RANGE (created_time); ) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id); CREATE INDEX IF NOT EXISTS idx_alarm_comment_alarm_id ON alarm_comment(alarm_id);
-- ALARM COMMENTS END
-- NOTIFICATIONS START
CREATE TABLE IF NOT EXISTS notification_target ( CREATE TABLE IF NOT EXISTS notification_target (
id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY, id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY,
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
@ -122,7 +126,7 @@ CREATE TABLE IF NOT EXISTS notification_rule (
additional_config VARCHAR(255), additional_config VARCHAR(255),
CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name)
); );
CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_trigger_type_created_time ON notification_rule(tenant_id, trigger_type, created_time DESC);
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (
id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY,
@ -139,9 +143,12 @@ CREATE TABLE IF NOT EXISTS notification_request (
status VARCHAR(32), status VARCHAR(32),
stats VARCHAR(10000) stats VARCHAR(10000)
); );
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_entity_type, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC)
CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id); WHERE originator_entity_type = 'USER';
CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status); CREATE INDEX IF NOT EXISTS idx_notification_request_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id)
WHERE originator_entity_type = 'ALARM';
CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status)
WHERE status = 'SCHEDULED';
CREATE TABLE IF NOT EXISTS notification ( CREATE TABLE IF NOT EXISTS notification (
id UUID NOT NULL, id UUID NOT NULL,
@ -150,13 +157,14 @@ CREATE TABLE IF NOT EXISTS notification (
recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL,
subject VARCHAR(255), subject VARCHAR(255),
text VARCHAR(1000) NOT NULL, body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000), additional_config VARCHAR(1000),
info VARCHAR(1000),
status VARCHAR(32) status VARCHAR(32)
) PARTITION BY RANGE (created_time); ) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_notification_id_recipient_id ON notification(id, recipient_id); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_status_created_time ON notification(recipient_id, status, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
-- NOTIFICATIONS END
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
@ -166,8 +174,6 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE
); );
-- ALARM COMMENTS END
-- ALARM INFO VIEW -- ALARM INFO VIEW
DROP VIEW IF EXISTS alarm_info CASCADE; DROP VIEW IF EXISTS alarm_info CASCADE;
@ -341,7 +347,7 @@ BEGIN
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
END IF; END IF;
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text; RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
END END
$$; $$;

1
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -208,6 +208,7 @@ public class TenantActor extends RuleChainManagerActor {
log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); log.trace("[{}] Ack message because Rule Engine is disabled", tenantId);
tbMsg.getCallback().onSuccess(); tbMsg.getCallback().onSuccess();
} }
systemContext.getNotificationRuleProcessingService().process(tenantId, tbMsg);
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {

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

@ -55,7 +55,7 @@ public class NotificationRuleController extends BaseController {
private final NotificationRuleService notificationRuleService; private final NotificationRuleService notificationRuleService;
@PostMapping("/rule") @PostMapping("/rule")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule) throws Exception { public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule) throws Exception {
notificationRule.setTenantId(getTenantId()); notificationRule.setTenantId(getTenantId());
checkEntity(notificationRule.getId(), notificationRule, NOTIFICATION); checkEntity(notificationRule.getId(), notificationRule, NOTIFICATION);
@ -63,14 +63,14 @@ public class NotificationRuleController extends BaseController {
} }
@GetMapping("/rule/{id}") @GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException { public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
NotificationRuleId notificationRuleId = new NotificationRuleId(id); NotificationRuleId notificationRuleId = new NotificationRuleId(id);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ); return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ);
} }
@GetMapping("/rules") @GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData<NotificationRuleInfo> getNotificationRules(@RequestParam int pageSize, public PageData<NotificationRuleInfo> getNotificationRules(@RequestParam int pageSize,
@RequestParam int page, @RequestParam int page,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ -83,7 +83,7 @@ public class NotificationRuleController extends BaseController {
} }
@DeleteMapping("/rule/{id}") @DeleteMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationRule(@PathVariable UUID id, public void deleteNotificationRule(@PathVariable UUID id,
@AuthenticationPrincipal SecurityUser user) throws Exception { @AuthenticationPrincipal SecurityUser user) throws Exception {
NotificationRuleId notificationRuleId = new NotificationRuleId(id); NotificationRuleId notificationRuleId = new NotificationRuleId(id);

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

@ -33,15 +33,16 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.page.PageData; 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.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -159,11 +160,8 @@ public class NotificationTargetController extends BaseController {
// generic permission for users // generic permission for users
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter(); UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter();
if (usersFilter.getType() == UsersFilterType.USER_LIST) { if (usersFilter.getType() == UsersFilterType.USER_LIST) {
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> { for (UUID recipientId : ((UserListFilter) usersFilter).getUsersIds()) {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink); checkUserId(new UserId(recipientId), Operation.READ);
}, 200);
for (User recipient : recipients) {
checkEntity(user, recipient, Operation.READ);
} }
} else if (usersFilter.getType() == UsersFilterType.CUSTOMER_USERS) { } else if (usersFilter.getType() == UsersFilterType.CUSTOMER_USERS) {
CustomerId customerId = new CustomerId(((CustomerUsersFilter) usersFilter).getCustomerId()); CustomerId customerId = new CustomerId(((CustomerUsersFilter) usersFilter).getCustomerId());

53
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultApiLimitService.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.apiusage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
@Service
@RequiredArgsConstructor
public class DefaultApiLimitService implements ApiLimitService {
private final EntityService entityService;
private final TbTenantProfileCache tenantProfileCache;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override
public boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType) {
DefaultTenantProfileConfiguration profileConfiguration = tenantProfileCache.get(tenantId).getDefaultProfileConfiguration();
long limit = profileConfiguration.getEntitiesLimit(entityType);
if (limit > 0) {
EntityTypeFilter filter = new EntityTypeFilter();
filter.setEntityType(entityType);
long currentCount = entityService.countEntitiesByQuery(tenantId, null, new EntityCountQuery(filter));
notificationRuleProcessingService.process(tenantId, entityType, limit, currentCount);
return currentCount < limit;
} else {
return true;
}
}
}

3
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -221,7 +222,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
} }
@Override @Override
public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo) { public void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo) {
logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo); logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo);
sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType)); sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType));
} }

3
application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -100,7 +101,7 @@ public interface TbNotificationEntityService {
void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType,
User user, Object... additionalInfo); User user, Object... additionalInfo);
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo); void notifyCreateOrUpdateAlarm(AlarmInfo alarm, ActionType actionType, User user, Object... additionalInfo);
void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user); void notifyAlarmComment(Alarm alarm, AlarmComment alarmComment, ActionType actionType, User user);

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

@ -22,12 +22,12 @@ import org.thingsboard.common.util.AbstractListeningExecutor;
@Component @Component
public class NotificationExecutorService extends AbstractListeningExecutor { public class NotificationExecutorService extends AbstractListeningExecutor {
@Value("${notification_system.thread_pool_size}") @Value("${notification_system.thread_pool_size:30}")
private int notificationSystemExecutorThreadPoolSize; private int threadPoolSize;
@Override @Override
protected int getThreadPollSize() { protected int getThreadPollSize() {
return notificationSystemExecutorThreadPoolSize; return threadPoolSize;
} }
} }

50
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -45,7 +45,7 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; 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.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
@ -82,7 +82,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"UnstableApiUsage", "rawtypes"}) @SuppressWarnings({"UnstableApiUsage", "rawtypes"})
public class DefaultNotificationCenter extends AbstractSubscriptionService implements NotificationCenter, NotificationChannel<User, PushDeliveryMethodNotificationTemplate> { public class DefaultNotificationCenter extends AbstractSubscriptionService implements NotificationCenter, NotificationChannel<User, WebDeliveryMethodNotificationTemplate> {
private final NotificationTargetService notificationTargetService; private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
@ -93,6 +93,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final DbCallbackExecutorService dbCallbackExecutorService; private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
private Map<NotificationDeliveryMethod, NotificationChannel> channels; private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@ -244,7 +245,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, PushDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, WebDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
NotificationRequest request = ctx.getRequest(); NotificationRequest request = ctx.getRequest();
Notification notification = Notification.builder() Notification notification = Notification.builder()
.requestId(request.getId()) .requestId(request.getId())
@ -264,8 +265,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.created(true)
.notification(notification) .notification(notification)
.updateType(ComponentLifecycleEvent.CREATED)
.build(); .build();
return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), update); return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), update);
} }
@ -282,8 +283,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
notification = notificationService.saveNotification(TenantId.SYS_TENANT_ID, notification); notification = notificationService.saveNotification(TenantId.SYS_TENANT_ID, notification);
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.created(true)
.notification(notification) .notification(notification)
.updateType(ComponentLifecycleEvent.CREATED)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
} }
@ -294,9 +295,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (updated) { if (updated) {
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId) .notificationId(notificationId)
.updatedStatus(NotificationStatus.READ) .newStatus(NotificationStatus.READ)
.updateType(ComponentLifecycleEvent.UPDATED)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
} }
@ -308,9 +309,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (updatedCount > 0) { if (updatedCount > 0) {
log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId); log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.allNotifications(true) .allNotifications(true)
.updatedStatus(NotificationStatus.READ) .newStatus(NotificationStatus.READ)
.updateType(ComponentLifecycleEvent.UPDATED)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
} }
@ -322,43 +323,28 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId); boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId);
if (deleted) { if (deleted) {
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.deleted(true)
.notification(notification) .notification(notification)
.updateType(ComponentLifecycleEvent.DELETED)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
} }
} }
@Override
public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Updating notification request {}", notificationRequest.getId());
notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
// marking related notifications as unread: TODO: causes each subscription to fetch notifications on each request update
notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT);
// TODO: no need to send request update for other than PLATFORM_USERS target type
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequest.getId())
.notificationInfo(notificationRequest.getInfo())
.deleted(false)
.build());
return notificationRequest;
}
@Override @Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) {
log.debug("Deleting notification request {}", notificationRequestId); log.debug("Deleting notification request {}", notificationRequestId);
NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId); NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);
notificationRequestService.deleteNotificationRequest(tenantId, notificationRequest); notificationRequestService.deleteNotificationRequest(tenantId, notificationRequestId);
// TODO: no need to send request update for other than PLATFORM_USERS target type
if (notificationRequest.isSent()) { if (notificationRequest.isSent()) {
// TODO: no need to send request update for other than PLATFORM_USERS target type
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId) .notificationRequestId(notificationRequestId)
.deleted(true) .deleted(true)
.build()); .build());
} else if (notificationRequest.isScheduled()) {
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
} }
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
} }
private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) {
@ -397,7 +383,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.PUSH; return NotificationDeliveryMethod.WEB;
} }
@Override @Override
@ -406,9 +392,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
@Autowired @Autowired
public void setChannels(List<NotificationChannel> channels, NotificationCenter websocketNotificationChannel) { public void setChannels(List<NotificationChannel> channels, NotificationCenter webNotificationChannel) {
this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c)); this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c));
this.channels.put(NotificationDeliveryMethod.PUSH, (NotificationChannel) websocketNotificationChannel); this.channels.put(NotificationDeliveryMethod.WEB, (NotificationChannel) webNotificationChannel);
} }
} }

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

@ -15,18 +15,15 @@
*/ */
package org.thingsboard.server.service.notification.rule; package org.thingsboard.server.service.notification.rule;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -42,61 +39,54 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject; import org.thingsboard.server.service.notification.rule.trigger.EntitiesLimitTriggerProcessor.EntitiesLimitTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
import java.util.Collection; import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@SuppressWarnings("rawtypes")
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService {
private final NotificationRuleService notificationRuleService; private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
@Autowired @Lazy @Autowired @Lazy
private NotificationCenter notificationCenter; private NotificationCenter notificationCenter;
private Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors;
private final NotificationExecutorService notificationExecutor; private final NotificationExecutorService notificationExecutor;
private final DbCallbackExecutorService dbCallbackExecutor;
private final Map<String, NotificationRuleTriggerType> msgTypeToTriggerType = Map.of( private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
DataConstants.INACTIVITY_EVENT, NotificationRuleTriggerType.DEVICE_INACTIVITY,
DataConstants.ENTITY_CREATED, NotificationRuleTriggerType.ENTITY_ACTION, private final Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
DataConstants.ENTITY_UPDATED, NotificationRuleTriggerType.ENTITY_ACTION,
DataConstants.ENTITY_DELETED, NotificationRuleTriggerType.ENTITY_ACTION,
DataConstants.COMMENT_CREATED, NotificationRuleTriggerType.ALARM_COMMENT,
DataConstants.COMMENT_UPDATED, NotificationRuleTriggerType.ALARM_COMMENT
);
@Override @Override
public void process(TenantId tenantId, TbMsg ruleEngineMsg) { public void process(TenantId tenantId, TbMsg ruleEngineMsg) {
String msgType = ruleEngineMsg.getType(); String msgType = ruleEngineMsg.getType();
NotificationRuleTriggerType triggerType = msgTypeToTriggerType.get(msgType); NotificationRuleTriggerType triggerType = ruleEngineMsgTypeToTriggerType.get(msgType);
if (triggerType == null) { if (triggerType == null) {
return; return;
} }
processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg); processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg);
} }
@Override @Override
public void process(TenantId tenantId, Alarm alarm, boolean deleted) { public void process(TenantId tenantId, AlarmApiCallResult alarmUpdate) {
AlarmTriggerObject triggerObject = AlarmTriggerObject.builder() processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarmUpdate.getAlarm().getId(), alarmUpdate);
.alarm(alarm)
.deleted(deleted)
.build();
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), triggerObject);
} }
@Override @Override
@ -112,73 +102,78 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
processTrigger(tenantId, NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, componentId, triggerObject); processTrigger(tenantId, NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, componentId, triggerObject);
} }
@Override
public void process(UpdateMessage platformUpdateMessage) {
// if (!partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition()) {
// return;
// }
// // todo: don't send repetitive notification after platform restart?
//
// processTrigger(TenantId.SYS_TENANT_ID, NotificationRuleTriggerType.NEW_PLATFORM_VERSION, TenantId.SYS_TENANT_ID, platformUpdateMessage);
}
@Override
public void process(TenantId tenantId, EntityType entityType, long limit, long currentCount) {
// EntitiesLimitTriggerObject triggerObject = EntitiesLimitTriggerObject.builder()
// .entityType(entityType)
// .limit(limit)
// .currentCount(currentCount)
// .build();
}
@Override
public void process(ComponentLifecycleMsg componentLifecycleMsg) {
// EntityId entityId = componentLifecycleMsg.getEntityId();
// switch (entityId.getEntityType()) {
// case TENANT:
//
// }
}
private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, Object triggerObject) { private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, Object triggerObject) {
ListenableFuture<List<NotificationRule>> rulesFuture = dbCallbackExecutor.submit(() -> { List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
return notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType); for (NotificationRule rule : rules) {
}); notificationExecutor.submit(() -> {
DonAsynchron.withCallback(rulesFuture, rules -> { try {
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
processNotificationRule(rule, originatorEntityId, triggerObject); processNotificationRule(rule, originatorEntityId, triggerObject);
}); } catch (Throwable e) {
} log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), triggerObject, e);
}, e -> { }
log.error("Failed to find notification rules by trigger type {}", triggerType, e); });
}); }
} }
private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) { private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (triggerConfig.getTriggerType().isUpdatable()) { if (matchesClearRule(triggerObject, triggerConfig)) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), originatorEntityId); List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), originatorEntityId);
if (!notificationRequests.isEmpty()) { if (notificationRequests.isEmpty()) {
if (matchesClearRule(triggerObject, triggerConfig)) {
notificationRequests = notificationRequests.stream()
.filter(notificationRequest -> {
if (!notificationRequest.isSent()) {
dbCallbackExecutor.submit(() -> {
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
});
return false;
} else {
return true;
}
})
.collect(Collectors.toList());
// not returning because we need to update notifications if any
}
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
for (NotificationRequest notificationRequest : notificationRequests) {
NotificationInfo previousNotificationInfo = notificationRequest.getInfo();
if (!notificationInfo.equals(previousNotificationInfo)) {
notificationRequest.setInfo(notificationInfo);
dbCallbackExecutor.submit(() -> {
notificationCenter.updateNotificationRequest(rule.getTenantId(), notificationRequest);
});
}
}
return; return;
} }
}
if (!matchesFilter(triggerObject, triggerConfig)) { List<UUID> targets = notificationRequests.stream()
.filter(NotificationRequest::isSent)
.flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
}
});
return; return;
} }
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig); if (matchesFilter(triggerObject, triggerConfig)) {
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> { NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
notificationExecutor.submit(() -> { rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
try { submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delay, targets);
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
} catch (Exception e) {
log.error("Failed to submit notification request for rule {}", rule.getId(), e);
}
}); });
}); }
} }
private boolean matchesFilter(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) { private boolean matchesFilter(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) {
@ -208,7 +203,14 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.ruleId(rule.getId()) .ruleId(rule.getId())
.originatorEntityId(originatorEntityId) .originatorEntityId(originatorEntityId)
.build(); .build();
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest); notificationExecutor.submit(() -> {
try {
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest);
} catch (Exception e) {
log.error("Failed to process notification request for rule {}", rule.getId(), e);
}
});
} }
@EventListener(ComponentLifecycleMsg.class) @EventListener(ComponentLifecycleMsg.class)
@ -220,7 +222,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
TenantId tenantId = componentLifecycleMsg.getTenantId(); TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId(); NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
dbCallbackExecutor.submit(() -> { notificationExecutor.submit(() -> {
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId); List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) { for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId); notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
@ -230,8 +232,15 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Autowired @Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) { public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
this.triggerProcessors = processors.stream() processors.forEach(processor -> {
.collect(Collectors.toMap(NotificationRuleTriggerProcessor::getTriggerType, p -> p)); triggerProcessors.put(processor.getTriggerType(), processor);
if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) {
Set<String> supportedMsgTypes = ((RuleEngineMsgNotificationRuleTriggerProcessor<?>) processor).getSupportedMsgTypes();
supportedMsgTypes.forEach(supportedMsgType -> {
ruleEngineMsgTypeToTriggerType.put(supportedMsgType, processor.getTriggerType());
});
}
});
} }
} }

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

@ -15,20 +15,30 @@
*/ */
package org.thingsboard.server.service.notification.rule; package org.thingsboard.server.service.notification.rule;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
public interface NotificationRuleProcessingService { public interface NotificationRuleProcessingService {
void process(TenantId tenantId, TbMsg ruleEngineMsg); void process(TenantId tenantId, TbMsg ruleEngineMsg);
void process(TenantId tenantId, Alarm alarm, boolean deleted); // for handling internal component lifecycle events that are not getting to rule chain
void process(ComponentLifecycleMsg componentLifecycleMsg);
void process(TenantId tenantId, AlarmApiCallResult alarmUpdate);
void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName,
EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error); EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error);
void process(UpdateMessage platformUpdateMessage);
void process(TenantId tenantId, EntityType entityType, long limit, long currentCount);
} }

79
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -0,0 +1,79 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmAssignmentNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
if (ruleEngineMsg.getType().equals(DataConstants.ALARM_UNASSIGN) && !triggerConfig.isNotifyOnUnassign()) {
return false;
}
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
}
@Override
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmInfo alarmInfo = JacksonUtil.fromString(ruleEngineMsg.getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder()
.assigneeFirstName(assignee != null ? assignee.getFirstName() : null)
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null)
.userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM_ASSIGNMENT;
}
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.ALARM_ASSIGN, DataConstants.ALARM_UNASSIGN);
}
}

42
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -17,30 +17,57 @@ package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service @Service
public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, AlarmCommentNotificationRuleTriggerConfig> { public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmCommentNotificationRuleTriggerConfig> {
@Override @Override
public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
return ruleEngineMsg.getMetaData().getValue("comment") != null; if (ruleEngineMsg.getMetaData().getValue("comment") == null) {
return false;
}
if (triggerConfig.isOnlyUserComments()) {
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class);
if (comment.getType() == AlarmCommentType.SYSTEM) {
return false;
}
}
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
} }
@Override @Override
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class); AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class);
Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class); AlarmInfo alarmInfo = JacksonUtil.fromString(ruleEngineMsg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder() return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText()) .comment(comment.getComment().get("text").asText())
.alarmType(alarm.getType()) .userName(ruleEngineMsg.getMetaData().getValue("userName"))
.alarmId(comment.getAlarmId().getId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build(); .build();
} }
@ -49,4 +76,9 @@ public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProc
return NotificationRuleTriggerType.ALARM_COMMENT; return NotificationRuleTriggerType.ALARM_COMMENT;
} }
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.COMMENT_CREATED, DataConstants.COMMENT_UPDATED);
}
} }

90
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -15,53 +15,92 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject; import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service @Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTriggerObject, AlarmNotificationRuleTriggerConfig> { public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmApiCallResult, AlarmNotificationRuleTriggerConfig> {
@Override @Override
public boolean matchesFilter(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm(); Alarm alarm = alarmUpdate.getAlarm();
return (CollectionUtils.isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && if (!typeMatches(alarm, triggerConfig)) {
(CollectionUtils.isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())); return false;
}
if (alarmUpdate.isCreated()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.CREATED)) {
return severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isSeverityChanged()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.SEVERITY_CHANGED)) {
return severityMatches(alarmUpdate.getOld(), triggerConfig) || severityMatches(alarm, triggerConfig);
} else {
// if we haven't yet sent notification about the alarm
return !severityMatches(alarmUpdate.getOld(), triggerConfig) && severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isAcknowledged()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.ACKNOWLEDGED)) {
return severityMatches(alarm, triggerConfig);
}
} else if (alarmUpdate.isCleared()) {
if (triggerConfig.getNotifyOn().contains(AlarmAction.CLEARED)) {
return severityMatches(alarm, triggerConfig);
}
}
return false;
} }
@Override @Override
public boolean matchesClearRule(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public boolean matchesClearRule(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
if (triggerObject.isDeleted()) { Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
return false;
}
if (alarmUpdate.isDeleted()) {
return true; return true;
} }
Alarm alarm = triggerObject.getAlarm();
ClearRule clearRule = triggerConfig.getClearRule(); ClearRule clearRule = triggerConfig.getClearRule();
if (clearRule != null) { if (clearRule != null) {
if (clearRule.getAlarmStatus() != null) { if (isNotEmpty(clearRule.getAlarmStatuses())) {
return clearRule.getAlarmStatus().equals(alarm.getStatus()); return AlarmStatusFilter.from(clearRule.getAlarmStatuses()).matches(alarm);
} }
} }
return false; return false;
} }
private boolean severityMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity());
}
private boolean typeMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType());
}
@Override @Override
public NotificationInfo constructNotificationInfo(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { public NotificationInfo constructNotificationInfo(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm(); // TODO: readable action
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder() return AlarmNotificationInfo.builder()
.alarmId(alarm.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarm.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarm.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())
.alarmSeverity(alarm.getSeverity()) .alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmStatus(alarm.getStatus()) .alarmSeverity(alarmInfo.getSeverity())
.alarmCustomerId(alarm.getCustomerId()) .alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build(); .build();
} }
@ -70,11 +109,4 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
return NotificationRuleTriggerType.ALARM; return NotificationRuleTriggerType.ALARM;
} }
@Data
@Builder
public static class AlarmTriggerObject {
private final Alarm alarm;
private final boolean deleted;
}
} }

11
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -28,9 +29,11 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.Set;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DeviceInactivityTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, DeviceInactivityNotificationRuleTriggerConfig> { public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceInactivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
@ -53,6 +56,7 @@ public class DeviceInactivityTriggerProcessor implements NotificationRuleTrigger
.deviceId(ruleEngineMsg.getOriginator().getId()) .deviceId(ruleEngineMsg.getOriginator().getId())
.deviceName(ruleEngineMsg.getMetaData().getValue("deviceName")) .deviceName(ruleEngineMsg.getMetaData().getValue("deviceName"))
.deviceType(ruleEngineMsg.getMetaData().getValue("deviceType")) .deviceType(ruleEngineMsg.getMetaData().getValue("deviceType"))
.deviceLabel(ruleEngineMsg.getMetaData().getValue("deviceLabel"))
.deviceCustomerId(ruleEngineMsg.getCustomerId()) .deviceCustomerId(ruleEngineMsg.getCustomerId())
.build(); .build();
} }
@ -62,4 +66,9 @@ public class DeviceInactivityTriggerProcessor implements NotificationRuleTrigger
return NotificationRuleTriggerType.DEVICE_INACTIVITY; return NotificationRuleTriggerType.DEVICE_INACTIVITY;
} }
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.INACTIVITY_EVENT);
}
} }

62
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -0,0 +1,62 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.Builder;
import lombok.Data;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.service.notification.rule.trigger.EntitiesLimitTriggerProcessor.EntitiesLimitTriggerObject;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@Service
public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerProcessor<EntitiesLimitTriggerObject, EntitiesLimitNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(EntitiesLimitTriggerObject triggerObject, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
if (isNotEmpty(triggerConfig.getEntityTypes()) && !triggerConfig.getEntityTypes().contains(triggerObject.getEntityType())) {
return false;
}
return ((float) triggerObject.getCurrentCount() / triggerObject.getLimit()) >= triggerConfig.getThreshold();
}
@Override
public NotificationInfo constructNotificationInfo(EntitiesLimitTriggerObject triggerObject, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
return EntitiesLimitNotificationInfo.builder()
.entityType(triggerObject.getEntityType())
.threshold((int) (triggerConfig.getThreshold() * 100))
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITIES_LIMIT;
}
@Data
@Builder
public static class EntitiesLimitTriggerObject {
private final EntityType entityType;
private final long limit;
private final long currentCount;
}
}

11
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java

@ -25,10 +25,11 @@ import org.thingsboard.server.common.data.notification.rule.trigger.EntityAction
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Service @Service
public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, EntityActionNotificationRuleTriggerConfig> { public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
@Override @Override
public boolean matchesFilter(TbMsg ruleEngineMsg, EntityActionNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(TbMsg ruleEngineMsg, EntityActionNotificationRuleTriggerConfig triggerConfig) {
@ -59,8 +60,7 @@ public class EntityActionTriggerProcessor implements NotificationRuleTriggerProc
msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED : msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED :
msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null; msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null;
return EntityActionNotificationInfo.builder() return EntityActionNotificationInfo.builder()
.entityType(entityId.getEntityType()) .entityId(entityId)
.entityId(entityId.getId())
.entityName(ruleEngineMsg.getMetaData().getValue("entityName")) .entityName(ruleEngineMsg.getMetaData().getValue("entityName"))
.actionType(actionType) .actionType(actionType)
.originatorUserId(UUID.fromString(ruleEngineMsg.getMetaData().getValue("userId"))) .originatorUserId(UUID.fromString(ruleEngineMsg.getMetaData().getValue("userId")))
@ -74,4 +74,9 @@ public class EntityActionTriggerProcessor implements NotificationRuleTriggerProc
return NotificationRuleTriggerType.ENTITY_ACTION; return NotificationRuleTriggerType.ENTITY_ACTION;
} }
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.ENTITY_CREATED, DataConstants.ENTITY_UPDATED, DataConstants.ENTITY_DELETED);
}
} }

45
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Service
public class NewPlatformVersionTriggerProcessor implements NotificationRuleTriggerProcessor<UpdateMessage, NewPlatformVersionNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(UpdateMessage triggerObject, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
return triggerObject.isUpdateAvailable();
}
@Override
public NotificationInfo constructNotificationInfo(UpdateMessage updateMessage, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
return NewPlatformVersionNotificationInfo.builder()
.message(updateMessage.getMessage())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
}
}

27
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
public interface RuleEngineMsgNotificationRuleTriggerProcessor<C extends NotificationRuleTriggerConfig> extends NotificationRuleTriggerProcessor<TbMsg, C> {
Set<String> getSupportedMsgTypes();
}

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

@ -156,10 +156,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
GitVersionControlQueueService vcQueueService, GitVersionControlQueueService vcQueueService,
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
Optional<JwtSettingsService> jwtSettingsService, Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) { NotificationSchedulerService notificationSchedulerService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

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

@ -129,9 +129,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher, PartitionService partitionService, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
TbServiceInfoProvider serviceInfoProvider, QueueService queueService) { TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory; this.submitStrategyFactory = submitStrategyFactory;
@ -482,7 +481,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
} }
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage()); msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage());
actorContext.tell(msg); actorContext.tell(msg);
notificationRuleProcessingService.process(tenantId, tbMsg);
} }
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}") @Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}")

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

@ -34,7 +34,6 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
@ -78,7 +77,6 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected final TbApiUsageStateService apiUsageStateService; protected final TbApiUsageStateService apiUsageStateService;
protected final PartitionService partitionService; protected final PartitionService partitionService;
protected final ApplicationEventPublisher eventPublisher; protected final ApplicationEventPublisher eventPublisher;
protected final NotificationRuleProcessingService notificationRuleProcessingService;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService; protected final Optional<JwtSettingsService> jwtSettingsService;
@ -88,7 +86,6 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher, PartitionService partitionService, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) { TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
this.actorContext = actorContext; this.actorContext = actorContext;
this.encodingService = encodingService; this.encodingService = encodingService;
@ -98,7 +95,6 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
this.notificationRuleProcessingService = notificationRuleProcessingService;
this.nfConsumer = nfConsumer; this.nfConsumer = nfConsumer;
this.jwtSettingsService = jwtSettingsService; this.jwtSettingsService = jwtSettingsService;
} }

2
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java

@ -18,7 +18,7 @@ package org.thingsboard.server.service.security.auth.mfa.provider.impl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil; import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig;

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

@ -139,6 +139,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private static final List<EntityKey> PERSISTENT_ENTITY_FIELDS = Arrays.asList( private static final List<EntityKey> PERSISTENT_ENTITY_FIELDS = Arrays.asList(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "type"), new EntityKey(EntityKeyType.ENTITY_FIELD, "type"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime")); new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
private final TenantService tenantService; private final TenantService tenantService;
@ -329,6 +330,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId()); DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName()); md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType()); md.putValue("deviceType", device.getType());
stateData.setMetaData(md); stateData.setMetaData(md);
callback.onSuccess(); callback.onSuccess();
@ -581,6 +583,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build(); .build();
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName()); md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType()); md.putValue("deviceType", device.getType());
DeviceStateData deviceStateData = DeviceStateData.builder() DeviceStateData deviceStateData = DeviceStateData.builder()
.customerId(device.getCustomerId()) .customerId(device.getCustomerId())
@ -660,6 +663,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build(); .build();
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "name", "")); md.putValue("deviceName", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "name", ""));
md.putValue("deviceLabel", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "label", ""));
md.putValue("deviceType", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "type", "")); md.putValue("deviceType", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "type", ""));
return DeviceStateData.builder() return DeviceStateData.builder()
.customerId(deviceIdInfo.getCustomerId()) .customerId(deviceIdInfo.getCustomerId())

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

@ -25,7 +25,6 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
@ -60,7 +58,6 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
@ -100,7 +97,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private final TbLocalSubscriptionService localSubscriptionService; private final TbLocalSubscriptionService localSubscriptionService;
private final DeviceStateService deviceStateService; private final DeviceStateService deviceStateService;
private final TbClusterService clusterService; private final TbClusterService clusterService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>();
@ -296,7 +292,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(), s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
alarm, false alarm, false
); );
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess(); callback.onSuccess();
} }
@ -313,7 +308,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs(), s -> alarm.getCreatedTime() >= s.getTs(),
alarm, true alarm, true
); );
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess(); callback.onSuccess();
} }

5
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil; import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -64,7 +64,6 @@ import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest; import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest;
import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; import org.thingsboard.server.service.sync.vc.data.CommitGitRequest;
import org.thingsboard.server.service.sync.vc.data.ContentsDiffGitRequest;
import org.thingsboard.server.service.sync.vc.data.EntitiesContentGitRequest; import org.thingsboard.server.service.sync.vc.data.EntitiesContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest; import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest; import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest;
@ -78,11 +77,9 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;

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

@ -52,6 +52,7 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection; import java.util.Collection;
@ -68,6 +69,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService; private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
@ -232,6 +234,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, result);
}); });
} }
@ -246,6 +249,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, result);
}); });
} }

11
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -19,12 +19,14 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -53,6 +55,9 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}") @Value("${updates.enabled}")
private boolean updatesEnabled; private boolean updatesEnabled;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture checkUpdatesFuture = null; private ScheduledFuture checkUpdatesFuture = null;
@ -121,12 +126,16 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform); request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version); request.put(VERSION_PARAM, version);
request.put(INSTANCE_ID_PARAM, instanceId.toString()); request.put(INSTANCE_ID_PARAM, instanceId.toString());
JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL+"/api/thingsboard/updates", request, JsonNode.class); JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/thingsboard/updates", request, JsonNode.class);
UpdateMessage prevUpdateMessage = updateMessage;
updateMessage = new UpdateMessage( updateMessage = new UpdateMessage(
response.get("message").asText(), response.get("message").asText(),
response.get("updateAvailable").asBoolean(), response.get("updateAvailable").asBoolean(),
version version
); );
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(updateMessage);
}
} catch (Exception e) { } catch (Exception e) {
log.trace(e.getMessage()); log.trace(e.getMessage());
} }

3
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -255,7 +255,8 @@ public class DefaultWebSocketService implements WebSocketService {
try { try {
cmdHandler.handle(sessionRef, cmd); cmdHandler.handle(sessionRef, cmd);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to handle WS cmd: {}", cmd, e); log.error("[sessionId: {}, tenantId: {}, userId: {}] Failed to handle WS cmd: {}", sessionId,
sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), cmd, e);
} }
} }
} }

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

@ -131,51 +131,44 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification(); Notification notification = update.getNotification();
UUID notificationId = update.getNotificationId(); UUID notificationId = update.getNotificationId();
switch (update.getUpdateType()) { if (update.isCreated()) {
case CREATED: { subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getLatestUnreadNotifications().put(notificationId, notification); subscription.getTotalUnreadCounter().incrementAndGet();
subscription.getTotalUnreadCounter().incrementAndGet(); if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) {
if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) { Set<UUID> beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit())
Set<UUID> beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit()) .map(IdBased::getUuidId).collect(Collectors.toSet());
.map(IdBased::getUuidId).collect(Collectors.toSet()); beyondLimit.forEach(id -> subscription.getLatestUnreadNotifications().remove(id));
beyondLimit.forEach(id -> subscription.getLatestUnreadNotifications().remove(id));
}
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
break;
}
case UPDATED: {
if (update.getUpdatedStatus() == NotificationStatus.READ) {
if (update.isAllNotifications() || subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} else {
subscription.getTotalUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createCountUpdate());
}
} else if (notification.getStatus() != NotificationStatus.READ) {
if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
}
}
break;
} }
case DELETED: { sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) { } else if (update.isUpdated()) {
if (update.getNewStatus() == NotificationStatus.READ) {
if (update.isAllNotifications() || subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
fetchUnreadNotifications(subscription); fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} else if (notification.getStatus() != NotificationStatus.READ) { } else {
subscription.getTotalUnreadCounter().decrementAndGet(); subscription.getTotalUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createCountUpdate()); sendUpdate(subscription.getSessionId(), subscription.createCountUpdate());
} }
break; } else if (notification.getStatus() != NotificationStatus.READ) {
if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
}
}
} else if (update.isDeleted()) {
if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} else if (notification.getStatus() != NotificationStatus.READ) {
subscription.getTotalUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createCountUpdate());
} }
} }
} }
private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
fetchUnreadNotifications(subscription); // FIXME: figure out how not to fetch notifications on each request update... fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} }
@ -191,47 +184,33 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) { private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification(); if (update.isCreated()) {
switch (update.getUpdateType()) { subscription.getUnreadCounter().incrementAndGet();
case CREATED: { sendUpdate(subscription.getSessionId(), subscription.createUpdate());
subscription.getUnreadCounter().incrementAndGet(); } else if (update.isUpdated()) {
sendUpdate(subscription.getSessionId(), subscription.createUpdate()); if (update.getNewStatus() == NotificationStatus.READ) {
break; if (update.isAllNotifications()) {
} fetchUnreadNotificationsCount(subscription);
case UPDATED: { } else {
if (update.getUpdatedStatus() == NotificationStatus.READ) {
if (update.isAllNotifications()) {
fetchUnreadNotificationsCount(subscription);
} else {
subscription.getUnreadCounter().decrementAndGet();
}
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
break;
}
case DELETED: {
if (notification.getStatus() != NotificationStatus.READ) {
subscription.getUnreadCounter().decrementAndGet(); subscription.getUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
} }
break; sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
} else if (update.isDeleted()) {
if (update.getNotification().getStatus() != NotificationStatus.READ) {
subscription.getUnreadCounter().decrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
} }
} }
} }
private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
fetchUnreadNotificationsCount(subscription); // FIXME: figure out how not to fetch notifications on each request update... fetchUnreadNotificationsCount(subscription);
sendUpdate(subscription.getSessionId(), subscription.createUpdate()); 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 @Override
public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) { public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) {
SecurityUser securityCtx = sessionRef.getSecurityCtx(); SecurityUser securityCtx = sessionRef.getSecurityCtx();
@ -253,4 +232,9 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId());
} }
private void sendUpdate(String sessionId, CmdUpdate update) {
log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update);
wsService.sendWsMsg(sessionId, update);
}
} }

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

@ -20,7 +20,6 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@ -28,6 +27,5 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@Builder @Builder
public class NotificationRequestUpdate { public class NotificationRequestUpdate {
private NotificationRequestId notificationRequestId; private NotificationRequestId notificationRequestId;
private NotificationInfo notificationInfo;
private boolean deleted; private boolean deleted;
} }

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

@ -22,7 +22,6 @@ import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.UUID; import java.util.UUID;
@ -33,12 +32,15 @@ import java.util.UUID;
public class NotificationUpdate { public class NotificationUpdate {
private NotificationId notificationId; private NotificationId notificationId;
private boolean created;
private Notification notification; private Notification notification;
boolean allNotifications; private boolean updated;
private NotificationStatus newStatus;
private boolean allNotifications;
private NotificationStatus updatedStatus; private boolean deleted;
private ComponentLifecycleEvent updateType;
public UUID getNotificationId() { public UUID getNotificationId() {
return notificationId != null ? notificationId.getId() : return notificationId != null ? notificationId.getId() :

3
application/src/main/resources/thingsboard.yml

@ -440,9 +440,6 @@ cache:
notificationRules: notificationRules:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}" maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}"
notificationRequests:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}"
attributes: attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"

5
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -884,7 +884,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected void awaitForDeviceActorToReceiveSubscription(DeviceId deviceId, FeatureType featureType, int subscriptionCount) { protected void awaitForDeviceActorToReceiveSubscription(DeviceId deviceId, FeatureType featureType, int subscriptionCount) {
DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId); DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
Map<UUID, SessionInfo> subscriptions = (Map<UUID, SessionInfo>) ReflectionTestUtils.getField(processor, getMapName(featureType)); Map<UUID, SessionInfo> subscriptions = (Map<UUID, SessionInfo>) ReflectionTestUtils.getField(processor, getMapName(featureType));
Awaitility.await("Device actor received subscription command from the transport").atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> subscriptions.size() == subscriptionCount); Awaitility.await("Device actor received subscription command from the transport").atMost(5, TimeUnit.SECONDS).until(() -> {
log.warn("device {}, subscriptions.size() == {}", deviceId, subscriptions.size());
return subscriptions.size() == subscriptionCount;
});
} }
protected static String getMapName(FeatureType featureType) { protected static String getMapName(FeatureType featureType) {

3
application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java

@ -671,6 +671,7 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
client.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(device.getId(), FeatureType.ATTRIBUTES, 1);
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
@ -690,7 +691,7 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(5, TimeUnit.SECONDS)); Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(30, TimeUnit.SECONDS));
Assert.assertEquals(JacksonUtil.OBJECT_MAPPER.createObjectNode().put(attrKey, attrValue), Assert.assertEquals(JacksonUtil.OBJECT_MAPPER.createObjectNode().put(attrKey, attrValue),
JacksonUtil.fromBytes(onUpdateCallback.getPayloadBytes())); JacksonUtil.fromBytes(onUpdateCallback.getPayloadBytes()));

8
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -101,7 +101,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
if (deliveryMethods.length == 0) { if (deliveryMethods.length == 0) {
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.PUSH}; deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB};
} }
NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods); NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods);
return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec); return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec);
@ -137,8 +137,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate; DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) { switch (deliveryMethod) {
case PUSH: { case WEB: {
PushDeliveryMethodNotificationTemplate template = new PushDeliveryMethodNotificationTemplate(); WebDeliveryMethodNotificationTemplate template = new WebDeliveryMethodNotificationTemplate();
template.setSubject(subject); template.setSubject(subject);
deliveryMethodNotificationTemplate = template; deliveryMethodNotificationTemplate = template;
break; break;

63
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -209,7 +209,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
int notificationsCount = 20; int notificationsCount = 20;
wsClient.registerWaitForUpdate(notificationsCount); wsClient.registerWaitForUpdate(notificationsCount);
for (int i = 1; i <= notificationsCount; i++) { for (int i = 1; i <= notificationsCount; i++) {
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.PUSH); submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
} }
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount); assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount);
@ -266,32 +266,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(getMyNotifications(false, 10)).size().isZero(); assertThat(getMyNotifications(false, 10)).size().isZero();
} }
@Test
public void whenNotificationRequestIsUpdated_thenUpdateNotifications() throws Exception {
wsClient.subscribeForUnreadNotifications(10);
wsClient.waitForReply(true);
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
String notificationText = "Text";
wsClient.registerWaitForUpdate();
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), notificationText);
wsClient.waitForUpdate(true);
Notification initialNotification = wsClient.getLastDataUpdate().getUpdate();
loginCustomerUser();
assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification);
assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getInfo());
wsClient.registerWaitForUpdate();
UserOriginatedNotificationInfo newNotificationInfo = new UserOriginatedNotificationInfo();
newNotificationInfo.setDescription("New description");
notificationRequest.setInfo(newNotificationInfo);
notificationCenter.updateNotificationRequest(tenantId, notificationRequest);
wsClient.waitForUpdate(true);
Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().iterator().next();
assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo);
assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification);
}
@Test @Test
public void testNotificationUpdatesForSeveralUsers() throws Exception { public void testNotificationUpdatesForSeveralUsers() throws Exception {
int usersCount = 150; int usersCount = 150;
@ -319,7 +293,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate()); sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate());
NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${recipientEmail}", 0, NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${recipientEmail}", 0,
NotificationDeliveryMethod.PUSH); NotificationDeliveryMethod.WEB);
await().atMost(10, TimeUnit.SECONDS) await().atMost(10, TimeUnit.SECONDS)
.pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> { .until(() -> {
@ -342,7 +316,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId()); NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(usersCount); assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(usersCount);
sessions.values().forEach(wsClient -> wsClient.registerWaitForUpdate()); sessions.values().forEach(wsClient -> wsClient.registerWaitForUpdate());
deleteNotificationRequest(notificationRequest.getId()); deleteNotificationRequest(notificationRequest.getId());
@ -393,17 +367,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
String requestorEmail = TENANT_ADMIN_EMAIL; String requestorEmail = TENANT_ADMIN_EMAIL;
NotificationTemplateConfig templateConfig = new NotificationTemplateConfig(); NotificationTemplateConfig templateConfig = new NotificationTemplateConfig();
templateConfig.setDefaultTextTemplate("Default message for SMS and PUSH: ${recipientEmail}"); templateConfig.setDefaultTextTemplate("Default message for SMS and WEB: ${recipientEmail}");
templateConfig.setNotificationSubject("Default subject for EMAIL: ${recipientEmail}"); templateConfig.setNotificationSubject("Default subject for EMAIL: ${recipientEmail}");
HashMap<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>(); HashMap<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>();
templateConfig.setDeliveryMethodsTemplates(templates); templateConfig.setDeliveryMethodsTemplates(templates);
notificationTemplate.setConfiguration(templateConfig); notificationTemplate.setConfiguration(templateConfig);
PushDeliveryMethodNotificationTemplate pushNotificationTemplate = new PushDeliveryMethodNotificationTemplate(); WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
pushNotificationTemplate.setEnabled(true); webNotificationTemplate.setEnabled(true);
// using default message for push // using default message for web
pushNotificationTemplate.setSubject("Subject for PUSH: ${recipientEmail}"); webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
templates.put(NotificationDeliveryMethod.PUSH, pushNotificationTemplate); templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
SmsDeliveryMethodNotificationTemplate smsNotificationTemplate = new SmsDeliveryMethodNotificationTemplate(); SmsDeliveryMethodNotificationTemplate smsNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
smsNotificationTemplate.setEnabled(true); smsNotificationTemplate.setEnabled(true);
@ -435,19 +409,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount); assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates(); Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
assertThat(processedTemplates.get(NotificationDeliveryMethod.PUSH)).asInstanceOf(type(PushDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH") .startsWith("Default message for SMS and WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail);
assertThat(template.getSubject()) assertThat(template.getSubject())
.startsWith("Subject for PUSH") .startsWith("Subject for WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail);
}); });
assertThat(processedTemplates.get(NotificationDeliveryMethod.SMS)).asInstanceOf(type(SmsDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.SMS)).asInstanceOf(type(SmsDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH") .startsWith("Default message for SMS and WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail);
}); });
assertThat(processedTemplates.get(NotificationDeliveryMethod.EMAIL)).asInstanceOf(type(EmailDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.EMAIL)).asInstanceOf(type(EmailDeliveryMethodNotificationTemplate.class))
@ -469,7 +443,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testNotificationRequestInfo() throws Exception { public void testNotificationRequestInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{ NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{
NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL
}; };
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods); NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods);
NotificationTarget target = createNotificationTarget(tenantAdminUserId); NotificationTarget target = createNotificationTarget(tenantAdminUserId);
@ -489,15 +463,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
NotificationTarget notificationTarget = createNotificationTarget(customerUserId); NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)", NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)",
NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS); NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.SMS);
wsClient.waitForUpdate(); wsClient.waitForUpdate();
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId()); NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne(); assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne();
} }
@ -526,7 +499,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTargetId notificationTargetId = notificationTarget.getId(); NotificationTargetId notificationTargetId = notificationTarget.getId();
ListenableFuture<NotificationRequest> request = executor.submit(() -> { ListenableFuture<NotificationRequest> request = executor.submit(() -> {
return submitNotificationRequest(notificationTargetId, "Hello, ${recipientEmail}", 0, NotificationDeliveryMethod.PUSH); return submitNotificationRequest(notificationTargetId, "Hello, ${recipientEmail}", 0, NotificationDeliveryMethod.WEB);
}); });
await().atMost(10, TimeUnit.SECONDS).until(request::isDone); await().atMost(10, TimeUnit.SECONDS).until(request::isDone);
NotificationRequest notificationRequest = request.get(); NotificationRequest notificationRequest = request.get();

36
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -23,6 +23,7 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.data.util.Pair; import org.springframework.data.util.Pair;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode; import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration; import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration;
@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmCondition;
@ -54,6 +56,7 @@ import org.thingsboard.server.common.data.notification.rule.EscalatedNotificatio
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
@ -91,6 +94,9 @@ import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = {
"js.evaluator=local"
})
public class NotificationRuleApiTest extends AbstractNotificationApiTest { public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@SpyBean @SpyBean
@ -110,10 +116,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception { public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${actionType}: ${entityType} [${entityId}]"; String notificationSubject = "${actionType}: ${entityType} [${entityId}]";
String notificationText = "User: ${originatorUserName}"; String notificationText = "User: ${originatorUserName}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH); NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
notificationRule.setName("Push-notification when any device is created, updated or deleted"); notificationRule.setName("Web notification when any device is created, updated or deleted");
notificationRule.setTemplateId(notificationTemplate.getId()); notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
@ -165,16 +171,17 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " + String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " +
"severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}"; "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}";
String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}"; String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH); NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
notificationRule.setName("Push-notification on any alarm"); notificationRule.setName("Web notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId()); notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM); notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null); triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null); triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
notificationRule.setTriggerConfig(triggerConfig); notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
@ -234,30 +241,28 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
}); });
clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate()); clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate());
alarmSubscriptionService.ackAlarm(tenantId, alarm.getId(), System.currentTimeMillis()); alarmSubscriptionService.acknowledgeAlarm(tenantId, alarm.getId(), System.currentTimeMillis());
AlarmStatus expectedStatus = AlarmStatus.ACTIVE_ACK; AlarmStatus expectedStatus = AlarmStatus.ACTIVE_ACK;
AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL; AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL;
clients.values().forEach(wsClient -> { clients.values().forEach(wsClient -> {
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().stream().findFirst().get(); Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate();
assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " + assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " +
"severity: " + expectedSeverity + ", deviceId: " + device.getId()); "severity: " + expectedSeverity + ", deviceId: " + device.getId());
assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity); assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity);
wsClient.close(); wsClient.close();
}); });
// TODO: test severity changes
} }
@Test @Test
public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception { public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception {
String notificationSubject = "${alarmSeverity} alarm '${alarmType}' is ${alarmStatus}"; String notificationSubject = "${alarmSeverity} alarm '${alarmType}' is ${alarmStatus}";
String notificationText = "${alarmId}"; String notificationText = "${alarmId}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH); NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
notificationRule.setName("Push-notification on any alarm"); notificationRule.setName("Web notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId()); notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM); notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
@ -268,9 +273,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(Set.of(alarmType)); triggerConfig.setAlarmTypes(Set.of(alarmType));
triggerConfig.setAlarmSeverities(null); triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED));
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule(); AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule();
clearRule.setAlarmStatus(AlarmStatus.CLEARED_UNACK); clearRule.setAlarmStatuses(Set.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.UNACK));
triggerConfig.setClearRule(clearRule); triggerConfig.setClearRule(clearRule);
notificationRule.setTriggerConfig(triggerConfig); notificationRule.setTriggerConfig(triggerConfig);
@ -308,9 +314,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo()); assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), null, System.currentTimeMillis()); alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null);
getWsClient().waitForUpdate(true); getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getNotifications().iterator().next(); notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("CRITICAL alarm '" + alarmType + "' is CLEARED_UNACK"); assertThat(notification.getSubject()).isEqualTo("CRITICAL alarm '" + alarmType + "' is CLEARED_UNACK");
assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty(); assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty();
@ -320,7 +326,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
public void testNotificationRuleProcessing_ruleEngineComponentLifecycleEvent_ruleNodeStartError() { public void testNotificationRuleProcessing_ruleEngineComponentLifecycleEvent_ruleNodeStartError() {
String subject = "Rule Node '${componentName}' in Rule Chain '${ruleChainName}' failed to start"; String subject = "Rule Node '${componentName}' in Rule Chain '${ruleChainName}' failed to start";
String text = "The error: ${error}"; String text = "The error: ${error}";
NotificationTemplate template = createNotificationTemplate(NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, subject, text, NotificationDeliveryMethod.PUSH); NotificationTemplate template = createNotificationTemplate(NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, subject, text, NotificationDeliveryMethod.WEB);
NotificationRule rule = new NotificationRule(); NotificationRule rule = new NotificationRule();
rule.setName("Rule node start-up failures in my rule chain"); rule.setName("Rule node start-up failures in my rule chain");
@ -360,7 +366,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testNotificationRuleInfo() throws Exception { public void testNotificationRuleInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL}; NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL};
NotificationTemplate template = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Subject", "Text", deliveryMethods); NotificationTemplate template = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Subject", "Text", deliveryMethods);
NotificationRule rule = new NotificationRule(); NotificationRule rule = new NotificationRule();

6
application/src/test/java/org/thingsboard/server/service/notification/NotificationTemplateApiTest.java

@ -86,9 +86,9 @@ public class NotificationTemplateApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testTemplatesSearch() throws Exception { public void testTemplatesSearch() throws Exception {
NotificationTemplate alarmNotificationTemplate = createNotificationTemplate(NotificationType.ALARM, "Alarm", "Alarm", NotificationDeliveryMethod.PUSH); NotificationTemplate alarmNotificationTemplate = createNotificationTemplate(NotificationType.ALARM, "Alarm", "Alarm", NotificationDeliveryMethod.WEB);
NotificationTemplate generalNotificationTemplate = createNotificationTemplate(NotificationType.GENERAL, "General", "General", NotificationDeliveryMethod.PUSH); NotificationTemplate generalNotificationTemplate = createNotificationTemplate(NotificationType.GENERAL, "General", "General", NotificationDeliveryMethod.WEB);
NotificationTemplate entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.PUSH); NotificationTemplate entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.WEB);
assertThat(findTemplates(NotificationType.ALARM)).extracting(IdBased::getId) assertThat(findTemplates(NotificationType.ALARM)).extracting(IdBased::getId)
.containsOnly(alarmNotificationTemplate.getId()); .containsOnly(alarmNotificationTemplate.getId());

13
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

@ -19,10 +19,8 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
@ -46,6 +44,7 @@ import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@ -116,10 +115,6 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@SuppressWarnings("deprecation")
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test @Test
public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception { public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception {
loginSysAdmin(); loginSysAdmin();
@ -138,9 +133,9 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try { try {
thrown.expect(DataValidationException.class); assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit)); .isInstanceOf(DataValidationException.class)
createResource("test1", 1 + DEFAULT_FILE_NAME); .hasMessageContaining("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit);
} finally { } finally {
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build()); defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build());
loginSysAdmin(); loginSysAdmin();

6
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java

@ -354,6 +354,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publishAndWait(attrPubTopic, CLIENT_ATTRIBUTES_PAYLOAD.getBytes()); client.publishAndWait(attrPubTopic, CLIENT_ATTRIBUTES_PAYLOAD.getBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
//RequestAttributes does not make any subscriptions in device actor
String update = getWsClient().waitForUpdate(); String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank(); assertThat(update).as("ws update received").isNotBlank();
MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1")); MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1"));
@ -383,6 +385,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publishAndWait(attrPubTopic, getAttributesProtoPayloadBytes()); client.publishAndWait(attrPubTopic, getAttributesProtoPayloadBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
//RequestAttributes does not make any subscriptions in device actor
String update = getWsClient().waitForUpdate(); String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank(); assertThat(update).as("ws update received").isNotBlank();
MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1")); MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1"));
@ -442,6 +446,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertThat(update).as("ws update received").isNotBlank(); assertThat(update).as("ws update received").isNotBlank();
client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE); client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE);
//RequestAttributes does not make any subscriptions in device actor
MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC); MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(clientAttributesCallback); client.setCallback(clientAttributesCallback);
@ -495,6 +500,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertThat(update).as("ws update received").isNotBlank(); assertThat(update).as("ws update received").isNotBlank();
client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE); client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE);
awaitForDeviceActorToReceiveSubscription(device.getId(), FeatureType.ATTRIBUTES, 1);
MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC); MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(clientAttributesCallback); client.setCallback(clientAttributesCallback);

1
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -223,7 +223,6 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
MqttTestCallback callback = new MqttTestCallback(GATEWAY_RPC_TOPIC); MqttTestCallback callback = new MqttTestCallback(GATEWAY_RPC_TOPIC);
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE);
subscribeAndCheckSubscription(client, GATEWAY_RPC_TOPIC, savedDevice.getId(), FeatureType.RPC); subscribeAndCheckSubscription(client, GATEWAY_RPC_TOPIC, savedDevice.getId(), FeatureType.RPC);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";

2
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/attributes/AbstractAttributesMqttV5Test.java

@ -21,6 +21,7 @@ import org.junit.Before;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import org.thingsboard.server.transport.mqtt.mqttv5.AbstractMqttV5Test; import org.thingsboard.server.transport.mqtt.mqttv5.AbstractMqttV5Test;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback;
@ -104,6 +105,7 @@ public abstract class AbstractAttributesMqttV5Test extends AbstractMqttV5Test {
MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback(); MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS); onUpdateCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/connection/AbstractMqttV5ClientConnectionTest.java

@ -25,6 +25,7 @@ import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
import org.junit.Assert; import org.junit.Assert;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient;
@ -101,6 +102,7 @@ public abstract class AbstractMqttV5ClientConnectionTest extends AbstractMqttInt
MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback(); MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
String payload = "{\"sharedStr\":\"" + StringUtils.repeat("*", valueLen) + "\"}"; String payload = "{\"sharedStr\":\"" + StringUtils.repeat("*", valueLen) + "\"}";

3
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/subscribe/AbstractMqttV5ClientSubscriptionTest.java

@ -22,6 +22,7 @@ import org.eclipse.paho.mqttv5.common.packet.MqttSubAck;
import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
import org.junit.Assert; import org.junit.Assert;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient;
@ -34,6 +35,7 @@ public abstract class AbstractMqttV5ClientSubscriptionTest extends AbstractMqttI
client.connectAndWait(accessToken); client.connectAndWait(accessToken);
IMqttToken subscriptionResult = client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE); IMqttToken subscriptionResult = client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
MqttWireMessage response = subscriptionResult.getResponse(); MqttWireMessage response = subscriptionResult.getResponse();
@ -52,6 +54,7 @@ public abstract class AbstractMqttV5ClientSubscriptionTest extends AbstractMqttI
client.connectAndWait(accessToken); client.connectAndWait(accessToken);
IMqttToken iMqttToken = client.subscribeAndWait("wrong/topic/+", MqttQoS.AT_MOST_ONCE); IMqttToken iMqttToken = client.subscribeAndWait("wrong/topic/+", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 0);
Assert.assertEquals(MESSAGE_TYPE_SUBACK,iMqttToken.getResponse().getType()); Assert.assertEquals(MESSAGE_TYPE_SUBACK,iMqttToken.getResponse().getType());
MqttSubAck subAck = (MqttSubAck) iMqttToken.getResponse(); MqttSubAck subAck = (MqttSubAck) iMqttToken.getResponse();
Assert.assertEquals(1, subAck.getReturnCodes().length); Assert.assertEquals(1, subAck.getReturnCodes().length);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/client/unsubscribe/AbstractMqttV5ClientUnsubscribeTest.java

@ -22,6 +22,7 @@ import org.eclipse.paho.mqttv5.common.packet.MqttUnsubAck;
import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage; import org.eclipse.paho.mqttv5.common.packet.MqttWireMessage;
import org.junit.Assert; import org.junit.Assert;
import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient;
@ -34,6 +35,7 @@ public abstract class AbstractMqttV5ClientUnsubscribeTest extends AbstractMqttIn
client.connectAndWait(accessToken); client.connectAndWait(accessToken);
client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
IMqttToken unsubscribeResult = client.unsubscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); IMqttToken unsubscribeResult = client.unsubscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
MqttWireMessage response = unsubscribeResult.getResponse(); MqttWireMessage response = unsubscribeResult.getResponse();
Assert.assertEquals(MESSAGE_TYPE_UNSUBACK, response.getType()); Assert.assertEquals(MESSAGE_TYPE_UNSUBACK, response.getType());

3
application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv5/rpc/AbstractMqttV5RpcTest.java

@ -22,6 +22,7 @@ import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage; import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.mqttv5.AbstractMqttV5Test; import org.thingsboard.server.transport.mqtt.mqttv5.AbstractMqttV5Test;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient; import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient;
@ -45,6 +46,7 @@ public abstract class AbstractMqttV5RpcTest extends AbstractMqttV5Test {
MqttV5TestCallback callback = new MqttV5TestCallback(DEVICE_RPC_REQUESTS_SUB_TOPIC.replace("+", "0")); MqttV5TestCallback callback = new MqttV5TestCallback(DEVICE_RPC_REQUESTS_SUB_TOPIC.replace("+", "0"));
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.RPC, 1);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String result = doPostAsync("/api/rpc/oneway/" + savedDevice.getId(), setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/oneway/" + savedDevice.getId(), setGpioRequest, String.class, status().isOk());
@ -59,6 +61,7 @@ public abstract class AbstractMqttV5RpcTest extends AbstractMqttV5Test {
MqttV5TestClient client = new MqttV5TestClient(); MqttV5TestClient client = new MqttV5TestClient();
client.connectAndWait(accessToken); client.connectAndWait(accessToken);
client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_LEAST_ONCE); client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_LEAST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.RPC, 1);
MqttV5TestRpcCallback callback = new MqttV5TestRpcCallback(client, DEVICE_RPC_REQUESTS_SUB_TOPIC.replace("+", "0")); MqttV5TestRpcCallback callback = new MqttV5TestRpcCallback(client, DEVICE_RPC_REQUESTS_SUB_TOPIC.replace("+", "0"));
client.setCallback(callback); client.setCallback(callback);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";

7
application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/attributes/AbstractMqttV5ClientSparkplugAttributesTest.java

@ -20,6 +20,7 @@ import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest; import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest;
import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType; import org.thingsboard.server.transport.mqtt.util.sparkplug.MetricDataType;
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType; import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType;
@ -50,6 +51,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
String SHARED_ATTRIBUTES_PAYLOAD = "{\"" + keyNodeRebirth + "\":" + value + "}"; String SHARED_ATTRIBUTES_PAYLOAD = "{\"" + keyNodeRebirth + "\":" + value + "}";
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedGateway.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedGateway.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
await(alias + SparkplugMessageType.NBIRTH.name()) await(alias + SparkplugMessageType.NBIRTH.name())
.atMost(40, TimeUnit.SECONDS) .atMost(40, TimeUnit.SECONDS)
@ -75,6 +77,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue); connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Boolean <-> String // Boolean <-> String
boolean expectedValue = true; boolean expectedValue = true;
@ -138,6 +141,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue); connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Long <-> String // Long <-> String
String valueStr = "123"; String valueStr = "123";
@ -189,6 +193,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue); connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Float <-> String // Float <-> String
String valueStr = "123.345"; String valueStr = "123.345";
@ -240,6 +245,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue); connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Double <-> String // Double <-> String
String valueStr = "123345456"; String valueStr = "123345456";
@ -291,6 +297,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue); connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// String <-> Long // String <-> Long
long valueLong = 123345456L; long valueLong = 123345456L;

4
application/src/test/java/org/thingsboard/server/transport/mqtt/sparkplug/rpc/AbstractMqttV5RpcSparkplugTest.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest; import org.thingsboard.server.transport.mqtt.sparkplug.AbstractMqttV5ClientSparkplugTest;
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType; import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType;
@ -45,6 +46,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32()); connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.RPC, 1);
String expected = "{\"result\":\"Success: " + SparkplugMessageType.NCMD.name() + "\"}"; String expected = "{\"result\":\"Success: " + SparkplugMessageType.NCMD.name() + "\"}";
String actual = sendRPCSparkplug(NCMD.name(), sparkplugRpcRequest, savedGateway); String actual = sendRPCSparkplug(NCMD.name(), sparkplugRpcRequest, savedGateway);
await(alias + SparkplugMessageType.NCMD.name()) await(alias + SparkplugMessageType.NCMD.name())
@ -79,6 +81,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32()); connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.RPC, 1);
String invalidateTypeMessageName = "RCMD"; String invalidateTypeMessageName = "RCMD";
String expected = "{\"result\":\"" + INVALID_ARGUMENTS + "\",\"error\":\"Failed to convert device RPC command to MQTT msg: " + String expected = "{\"result\":\"" + INVALID_ARGUMENTS + "\",\"error\":\"Failed to convert device RPC command to MQTT msg: " +
invalidateTypeMessageName + "{\\\"metricName\\\":\\\"" + metricBirthName_Int32 + "\\\",\\\"value\\\":" + metricBirthValue_Int32 + "}\"}"; invalidateTypeMessageName + "{\\\"metricName\\\":\\\"" + metricBirthName_Int32 + "\\\",\\\"value\\\":" + metricBirthValue_Int32 + "}\"}";
@ -92,6 +95,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32()); connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected()); Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE); client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.RPC, 1);
String metricNameBad = metricBirthName_Int32 + "_Bad"; String metricNameBad = metricBirthName_Int32 + "_Bad";
String sparkplugRpcRequestBad = "{\"metricName\":\"" + metricNameBad + "\",\"value\":" + metricBirthValue_Int32 + "}"; String sparkplugRpcRequestBad = "{\"metricName\":\"" + metricNameBad + "\",\"value\":" + metricBirthValue_Int32 + "}";
String expected = "{\"result\":\"BAD_REQUEST_PARAMS\",\"error\":\"Failed send To Node Rpc Request: " + String expected = "{\"result\":\"BAD_REQUEST_PARAMS\",\"error\":\"Failed send To Node Rpc Request: " +

13
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java

@ -32,16 +32,18 @@ public class AlarmApiCallResult {
private final boolean created; private final boolean created;
private final boolean modified; private final boolean modified;
private final boolean cleared; private final boolean cleared;
private final boolean deleted;
private final AlarmInfo alarm; private final AlarmInfo alarm;
private final Alarm old; private final Alarm old;
private final List<EntityId> propagatedEntitiesList; private final List<EntityId> propagatedEntitiesList;
@Builder @Builder
private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) { private AlarmApiCallResult(boolean successful, boolean created, boolean modified, boolean cleared, boolean deleted, AlarmInfo alarm, Alarm old, List<EntityId> propagatedEntitiesList) {
this.successful = successful; this.successful = successful;
this.created = created; this.created = created;
this.modified = modified; this.modified = modified;
this.cleared = cleared; this.cleared = cleared;
this.deleted = deleted;
this.alarm = alarm; this.alarm = alarm;
this.old = old; this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList; this.propagatedEntitiesList = propagatedEntitiesList;
@ -52,6 +54,7 @@ public class AlarmApiCallResult {
this.created = other.created; this.created = other.created;
this.modified = other.modified; this.modified = other.modified;
this.cleared = other.cleared; this.cleared = other.cleared;
this.deleted = other.deleted;
this.alarm = other.alarm; this.alarm = other.alarm;
this.old = other.old; this.old = other.old;
this.propagatedEntitiesList = propagatedEntitiesList; this.propagatedEntitiesList = propagatedEntitiesList;
@ -65,6 +68,14 @@ public class AlarmApiCallResult {
} }
} }
public boolean isAcknowledged() {
if (alarm == null || old == null) {
return false;
} else {
return alarm.isAcknowledged() != old.isAcknowledged();
}
}
public AlarmSeverity getOldSeverity() { public AlarmSeverity getOldSeverity() {
return isSeverityChanged() ? old.getSeverity() : null; return isSeverityChanged() ? old.getSeverity() : null;
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java

@ -45,7 +45,7 @@ public interface NotificationRequestService {
List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId);
void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId);
PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink); PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java

@ -40,8 +40,6 @@ public interface NotificationService {
int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId); int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId);
void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);
} }

25
common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiLimitService.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.usagerecord;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public interface ApiLimitService {
boolean checkEntitiesLimit(TenantId tenantId, EntityType entityType);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java

@ -60,7 +60,7 @@ public interface UserService extends EntityDaoService {
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink); PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
PageData<User> findUsers(TenantId tenantId, PageLink pageLink); PageData<User> findAllUsers(TenantId tenantId, PageLink pageLink);
void deleteTenantAdmins(TenantId tenantId); void deleteTenantAdmins(TenantId tenantId);

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -30,7 +30,6 @@ public class CacheConstants {
public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String NOTIFICATION_RULES_CACHE = "notificationRules"; public static final String NOTIFICATION_RULES_CACHE = "notificationRules";
public static final String NOTIFICATION_REQUESTS_CACHE = "notificationRequests";
public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes"; public static final String ATTRIBUTES_CACHE = "attributes";

18
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmStatusFilter.java

@ -15,7 +15,7 @@
*/ */
package org.thingsboard.server.common.data.alarm; package org.thingsboard.server.common.data.alarm;
import java.util.List; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
public class AlarmStatusFilter { public class AlarmStatusFilter {
@ -94,19 +94,19 @@ public class AlarmStatusFilter {
} }
public static AlarmStatusFilter fromList(List<AlarmSearchStatus> list) { public static AlarmStatusFilter from(Collection<AlarmSearchStatus> statuses) {
if (list == null || list.isEmpty() || list.contains(AlarmSearchStatus.ANY)) { if (statuses == null || statuses.isEmpty() || statuses.contains(AlarmSearchStatus.ANY)) {
return EMPTY; return EMPTY;
} }
boolean clearFilter = list.contains(AlarmSearchStatus.CLEARED); boolean clearFilter = statuses.contains(AlarmSearchStatus.CLEARED);
boolean activeFilter = list.contains(AlarmSearchStatus.ACTIVE); boolean activeFilter = statuses.contains(AlarmSearchStatus.ACTIVE);
Optional<Boolean> clear = Optional.empty(); Optional<Boolean> clear = Optional.empty();
if (clearFilter && !activeFilter || !clearFilter && activeFilter) { if (clearFilter && !activeFilter || !clearFilter && activeFilter) {
clear = Optional.of(clearFilter); clear = Optional.of(clearFilter);
} }
boolean ackFilter = list.contains(AlarmSearchStatus.ACK); boolean ackFilter = statuses.contains(AlarmSearchStatus.ACK);
boolean unackFilter = list.contains(AlarmSearchStatus.UNACK); boolean unackFilter = statuses.contains(AlarmSearchStatus.UNACK);
Optional<Boolean> ack = Optional.empty(); Optional<Boolean> ack = Optional.empty();
if (ackFilter && !unackFilter || !ackFilter && unackFilter) { if (ackFilter && !unackFilter || !ackFilter && unackFilter) {
ack = Optional.of(ackFilter); ack = Optional.of(ackFilter);
@ -114,5 +114,9 @@ public class AlarmStatusFilter {
return new AlarmStatusFilter(clear, ack); return new AlarmStatusFilter(clear, ack);
} }
public boolean matches(Alarm alarm) {
return ackFilter.map(ackFilter -> ackFilter.equals(alarm.isAcknowledged())).orElse(true) &&
clearFilter.map(clearedFilter -> clearedFilter.equals(alarm.isCleared())).orElse(true);
}
} }

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -46,14 +45,4 @@ public class Notification extends BaseData<NotificationId> {
private NotificationStatus status; private NotificationStatus status;
@JsonProperty("text")
public String getProcessedText() {
return NotificationProcessingContext.processTemplate(text, info);
}
@JsonProperty("subject")
public String getProcessedSubject() {
return NotificationProcessingContext.processTemplate(subject, info);
}
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java

@ -21,7 +21,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum NotificationDeliveryMethod { public enum NotificationDeliveryMethod {
PUSH("push-notification"), WEB("web"),
EMAIL("email"), EMAIL("email"),
SMS("SMS"), SMS("SMS"),
SLACK("Slack"); SLACK("Slack");

13
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java

@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.HasSubject; import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
@ -96,7 +96,7 @@ public class NotificationProcessingContext {
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) { public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
NotificationInfo info = request.getInfo(); NotificationInfo info = request.getInfo();
if (info != null && deliveryMethod != NotificationDeliveryMethod.PUSH) { // for push notifications we are processing template from info on each serialization if (info != null) {
templateContext = new HashMap<>(templateContext); templateContext = new HashMap<>(templateContext);
templateContext.putAll(info.getTemplateData()); templateContext.putAll(info.getTemplateData());
} }
@ -108,9 +108,9 @@ public class NotificationProcessingContext {
((HasSubject) template).setSubject(processTemplate(subject, templateContext)); ((HasSubject) template).setSubject(processTemplate(subject, templateContext));
} }
if (deliveryMethod == NotificationDeliveryMethod.PUSH) { if (deliveryMethod == NotificationDeliveryMethod.WEB) {
PushDeliveryMethodNotificationTemplate pushNotificationTemplate = (PushDeliveryMethodNotificationTemplate) template; WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(pushNotificationTemplate.getAdditionalConfig()) Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig())
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject) .map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
.map(config -> (ObjectNode) config); .map(config -> (ObjectNode) config);
if (buttonConfig.isPresent()) { if (buttonConfig.isPresent()) {
@ -129,7 +129,8 @@ public class NotificationProcessingContext {
String result = template; String result = template;
for (Map<String, String> context : contexts) { for (Map<String, String> context : contexts) {
for (Map.Entry<String, String> kv : context.entrySet()) { for (Map.Entry<String, String> kv : context.entrySet()) {
result = result.replace("${" + kv.getKey() + '}', kv.getValue()); String value = Strings.nullToEmpty(kv.getValue());
result = result.replace("${" + kv.getKey() + '}', value);
} }
} }
return result; return result;

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java

@ -22,6 +22,9 @@ public enum NotificationType {
DEVICE_INACTIVITY, DEVICE_INACTIVITY,
ENTITY_ACTION, ENTITY_ACTION,
ALARM_COMMENT, ALARM_COMMENT,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION,
ENTITIES_LIMIT
} }

78
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificationInfo {
private String assigneeFirstName;
private String assigneeLastName;
private String assigneeEmail;
private String userName;
private String alarmType;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"assigneeFirstName", assigneeFirstName,
"assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail,
"userName", userName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
);
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;
}
}

34
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java

@ -19,27 +19,55 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Builder @Builder
public class AlarmCommentNotificationInfo implements NotificationInfo { public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationInfo {
private String comment; private String comment;
private String userName;
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"comment", comment, "comment", comment,
"userName", userName,
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString() "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString(),
"alarmOriginatorName", alarmOriginatorName
); );
} }
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;
}
} }

22
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java

@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -36,25 +38,33 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
private EntityId alarmOriginator; private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity; private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus; private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId; private CustomerId alarmCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( // TODO: readable status change
return mapOf(
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(), "alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(), "alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorName", alarmOriginatorName,
"alarmOriginatorId", alarmOriginator.getId().toString() "alarmOriginatorId", alarmOriginator.getId().toString()
); );
} }
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public EntityId getStateEntityId() {
return alarmOriginator;
}
} }

14
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceInactivityNotificationInfo.java

@ -15,15 +15,20 @@
*/ */
package org.thingsboard.server.common.data.notification.info; package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -32,6 +37,7 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
private UUID deviceId; private UUID deviceId;
private String deviceName; private String deviceName;
private String deviceLabel;
private String deviceType; private String deviceType;
private CustomerId deviceCustomerId; private CustomerId deviceCustomerId;
@ -42,11 +48,17 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"deviceId", deviceId.toString(), "deviceId", deviceId.toString(),
"deviceName", deviceName, "deviceName", deviceName,
"deviceLabel", deviceLabel,
"deviceType", deviceType "deviceType", deviceType
); );
} }
@Override
public EntityId getStateEntityId() {
return new DeviceId(deviceId);
}
} }

28
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheKey.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java

@ -13,31 +13,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.cache; package org.thingsboard.server.common.data.notification.info;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import java.io.Serializable; import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
@Builder @Builder
public class NotificationRequestCacheKey implements Serializable { public class EntitiesLimitNotificationInfo implements NotificationInfo {
private static final long serialVersionUID = 59871139005482170L; private EntityType entityType;
private int threshold;
private EntityId originatorEntityId;
private NotificationRuleId ruleId;
@Override @Override
public String toString() { public Map<String, String> getTemplateData() {
return ruleId + "_" + originatorEntityId; // FIXME: readable entity type name, e.g. 'Devices'
return mapOf(
"entityType", entityType.name(),
"threshold", String.valueOf(threshold)
);
} }
} }

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java

@ -19,21 +19,22 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class EntityActionNotificationInfo implements RuleOriginatedNotificationInfo { public class EntityActionNotificationInfo implements RuleOriginatedNotificationInfo {
private EntityType entityType; private EntityId entityId;
private UUID entityId;
private String entityName; private String entityName;
private ActionType actionType; private ActionType actionType;
private UUID originatorUserId; private UUID originatorUserId;
@ -47,8 +48,8 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"entityType", entityType.name(), "entityType", entityId.getEntityType().name(),
"entityId", entityId.toString(), "entityId", entityId.toString(),
"entityName", entityName, "entityName", entityName,
"actionType", actionType.name().toLowerCase(), "actionType", actionType.name().toLowerCase(),
@ -57,4 +58,9 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
); );
} }
@Override
public EntityId getStateEntityId() {
return entityId;
}
} }

22
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheValue.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -13,26 +13,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.cache; package org.thingsboard.server.common.data.notification.info;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import java.io.Serializable; import java.util.Map;
import java.util.List;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
@Builder @Builder
public class NotificationRequestCacheValue implements Serializable { public class NewPlatformVersionNotificationInfo implements NotificationInfo {
private static final long serialVersionUID = 950211234585105415L; private String message;
private List<NotificationRequest> notificationRequests; @Override
public Map<String, String> getTemplateData() {
return mapOf(
"message", message
);
}
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.info;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map; import java.util.Map;
@ -28,4 +29,8 @@ public interface NotificationInfo {
@JsonIgnore @JsonIgnore
Map<String, String> getTemplateData(); Map<String, String> getTemplateData();
default EntityId getStateEntityId() {
return null;
}
} }

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java

@ -15,18 +15,18 @@
*/ */
package org.thingsboard.server.common.data.notification.info; package org.thingsboard.server.common.data.notification.info;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.Map; import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@ -42,7 +42,7 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return Map.of( return mapOf(
"ruleChainId", ruleChainId.toString(), "ruleChainId", ruleChainId.toString(),
"ruleChainName", ruleChainName, "ruleChainName", ruleChainName,
"componentId", componentId.toString(), "componentId", componentId.toString(),
@ -53,4 +53,9 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
); );
} }
@Override
public EntityId getStateEntityId() {
return ruleChainId;
}
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java

@ -43,4 +43,9 @@ public class RuleEngineOriginatedNotificationInfo implements NotificationInfo {
return templateData; return templateData;
} }
@Override
public EntityId getStateEntityId() {
return msgOriginator;
}
} }

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java

@ -47,6 +47,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@NotNull @NotNull
private NotificationRuleTriggerType triggerType; private NotificationRuleTriggerType triggerType;
@NotNull @NotNull
@Valid
private NotificationRuleTriggerConfig triggerConfig; private NotificationRuleTriggerConfig triggerConfig;
@NotNull @NotNull
@Valid @Valid

37
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmAssignmentNotificationRuleTriggerConfig.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.Set;
@Data
public class AlarmAssignmentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean notifyOnUnassign;
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM_ASSIGNMENT;
}
}

9
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java

@ -16,10 +16,19 @@
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.Set;
@Data @Data
public class AlarmCommentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { public class AlarmCommentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean onlyUserComments;
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM_COMMENT; return NotificationRuleTriggerType.ALARM_COMMENT;

12
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java

@ -16,9 +16,10 @@
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import javax.validation.constraints.NotEmpty;
import java.util.Set; import java.util.Set;
@Data @Data
@ -26,6 +27,9 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<String> alarmTypes; private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities; private Set<AlarmSeverity> alarmSeverities;
@NotEmpty
private Set<AlarmAction> notifyOn;
private ClearRule clearRule; private ClearRule clearRule;
@Override @Override
@ -35,7 +39,11 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
@Data @Data
public static class ClearRule { public static class ClearRule {
private AlarmStatus alarmStatus; private Set<AlarmSearchStatus> alarmStatuses;
}
public enum AlarmAction {
CREATED, SEVERITY_CHANGED, ACKNOWLEDGED, CLEARED
} }
} }

36
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntitiesLimitNotificationRuleTriggerConfig.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import javax.validation.constraints.Max;
import java.util.Set;
@Data
public class EntitiesLimitNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<EntityType> entityTypes;
@Max(1)
private float threshold; // in percents,
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITIES_LIMIT;
}
}

28
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionNotificationRuleTriggerConfig.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data;
@Data
public class NewPlatformVersionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java

@ -27,7 +27,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"), @Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"),
@Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"), @Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"),
@Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"), @Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"),
@Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT") @Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT"),
@Type(value = AlarmAssignmentNotificationRuleTriggerConfig.class, name = "ALARM_ASSIGNMENT"),
@Type(value = NewPlatformVersionNotificationRuleTriggerConfig.class, name = "NEW_PLATFORM_VERSION"),
@Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT")
}) })
public interface NotificationRuleTriggerConfig { public interface NotificationRuleTriggerConfig {

20
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java

@ -15,19 +15,15 @@
*/ */
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum NotificationRuleTriggerType { public enum NotificationRuleTriggerType {
ALARM(true), ALARM,
ALARM_COMMENT(true), ALARM_COMMENT,
DEVICE_INACTIVITY(false), DEVICE_INACTIVITY,
ENTITY_ACTION(false), ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT(false); RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
@Getter NEW_PLATFORM_VERSION,
private final boolean updatable; ENTITIES_LIMIT
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java

@ -24,7 +24,7 @@ import java.util.Set;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum NotificationTargetType { public enum NotificationTargetType {
PLATFORM_USERS(Set.of(NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS)), PLATFORM_USERS(Set.of(NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS)),
SLACK(Set.of(NotificationDeliveryMethod.SLACK)); SLACK(Set.of(NotificationDeliveryMethod.SLACK));
@Getter @Getter

4
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/platform/UsersFilterType.java

@ -20,8 +20,4 @@ public enum UsersFilterType {
CUSTOMER_USERS, CUSTOMER_USERS,
ALL_USERS, ALL_USERS,
ORIGINATOR_ENTITY_OWNER_USERS ORIGINATOR_ENTITY_OWNER_USERS
// USER_GROUP,
// USERS_WITH_ROLE,
// QUERY // ?
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method")
@JsonSubTypes({ @JsonSubTypes({
@Type(name = "PUSH", value = PushDeliveryMethodNotificationTemplate.class), @Type(name = "WEB", value = WebDeliveryMethodNotificationTemplate.class),
@Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class), @Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class),
@Type(name = "SMS", value = SmsDeliveryMethodNotificationTemplate.class), @Type(name = "SMS", value = SmsDeliveryMethodNotificationTemplate.class),
@Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class) @Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class)

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java

@ -26,12 +26,12 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
private String subject; private String subject;
private JsonNode additionalConfig; private JsonNode additionalConfig;
public PushDeliveryMethodNotificationTemplate(PushDeliveryMethodNotificationTemplate other) { public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
super(other); super(other);
this.subject = other.subject; this.subject = other.subject;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig;
@ -39,12 +39,12 @@ public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotifi
@Override @Override
public NotificationDeliveryMethod getMethod() { public NotificationDeliveryMethod getMethod() {
return NotificationDeliveryMethod.PUSH; return NotificationDeliveryMethod.WEB;
} }
@Override @Override
public PushDeliveryMethodNotificationTemplate copy() { public WebDeliveryMethodNotificationTemplate copy() {
return new PushDeliveryMethodNotificationTemplate(this); return new WebDeliveryMethodNotificationTemplate(this);
} }
} }

20
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -20,6 +20,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType; import org.thingsboard.server.common.data.TenantProfileType;
@AllArgsConstructor @AllArgsConstructor
@ -107,6 +108,25 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8)); return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8));
} }
public long getEntitiesLimit(EntityType entityType) {
switch (entityType) {
case DEVICE:
return maxDevices;
case ASSET:
return maxAssets;
case CUSTOMER:
return maxCustomers;
case USER:
return maxUsers;
case DASHBOARD:
return maxDashboards;
case RULE_CHAIN:
return maxRuleChains;
default:
return 0;
}
}
@Override @Override
public TenantProfileType getType() { public TenantProfileType getType() {
return TenantProfileType.DEFAULT; return TenantProfileType.DEFAULT;

22
common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java → common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java

@ -13,9 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.common.util; package org.thingsboard.server.common.data.util;
import com.google.common.collect.ImmutableMap;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -47,4 +52,19 @@ public class CollectionsUtil {
return count; return count;
} }
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> mapOf(Object... kvs) {
Map<K, V> map = new HashMap<>();
for (int i = 0; i < kvs.length; i += 2) {
K key = (K) kvs[i];
V value = (V) kvs[i + 1];
map.put(key, value);
}
return map;
}
public static <K, V> Map<K, V> unmodifiableMapOf(Object... kvs) {
return Collections.unmodifiableMap(mapOf(kvs));
}
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/model/LwM2MModelConfig.java

@ -27,7 +27,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.CollectionsUtil.diffSets; import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
@Data @Data
@NoArgsConstructor @NoArgsConstructor

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -103,7 +103,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.common.util.CollectionsUtil.diffSets; import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD;

5
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -1060,7 +1060,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
private void checkSparkplugNodeSession(MqttConnectMessage connectMessage, ChannelHandlerContext ctx) { private void checkSparkplugNodeSession(MqttConnectMessage connectMessage, ChannelHandlerContext ctx, SessionMetaData sessionMetaData) {
try { try {
if (sparkplugSessionHandler == null) { if (sparkplugSessionHandler == null) {
SparkplugTopic sparkplugTopicNode = validatedSparkplugTopicConnectedNode(connectMessage); SparkplugTopic sparkplugTopicNode = validatedSparkplugTopicConnectedNode(connectMessage);
@ -1069,6 +1069,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
sparkplugSessionHandler = new SparkplugNodeSessionHandler(this, deviceSessionCtx, sessionId, sparkplugTopicNode); sparkplugSessionHandler = new SparkplugNodeSessionHandler(this, deviceSessionCtx, sessionId, sparkplugTopicNode);
sparkplugSessionHandler.onAttributesTelemetryProto(0, sparkplugBProtoNode, sparkplugSessionHandler.onAttributesTelemetryProto(0, sparkplugBProtoNode,
deviceSessionCtx.getDeviceInfo().getDeviceName(), sparkplugTopicNode); deviceSessionCtx.getDeviceInfo().getDeviceName(), sparkplugTopicNode);
sessionMetaData.setOverwriteActivityTime(true);
} else { } else {
log.trace("[{}][{}] Failed to fetch sparkplugDevice connect: sparkplugTopicName without SparkplugMessageType.NDEATH.", sessionId, deviceSessionCtx.getDeviceInfo().getDeviceName()); log.trace("[{}][{}] Failed to fetch sparkplugDevice connect: sparkplugTopicName without SparkplugMessageType.NDEATH.", sessionId, deviceSessionCtx.getDeviceInfo().getDeviceName());
throw new ThingsboardException("Invalid request body", ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Invalid request body", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
@ -1145,7 +1146,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
public void onSuccess(Void msg) { public void onSuccess(Void msg) {
SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this); SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this);
if (deviceSessionCtx.isSparkplug()) { if (deviceSessionCtx.isSparkplug()) {
checkSparkplugNodeSession(connectMessage, ctx); checkSparkplugNodeSession(connectMessage, ctx, sessionMetaData);
} else { } else {
checkGatewaySession(sessionMetaData); checkGatewaySession(sessionMetaData);
} }

2
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java

@ -29,7 +29,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.CollectionsUtil; import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;

2
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -185,7 +185,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} else { } else {
deleteEntityRelations(tenantId, alarm.getId()); deleteEntityRelations(tenantId, alarm.getId());
alarmDao.removeById(tenantId, alarm.getUuidId()); alarmDao.removeById(tenantId, alarm.getUuidId());
return AlarmApiCallResult.builder().alarm(alarm).successful(true).build(); return AlarmApiCallResult.builder().alarm(alarm).deleted(true).successful(true).build();
} }
} }

21
dao/src/main/java/org/thingsboard/server/dao/entity/DefaultEntityServiceRegistry.java

@ -15,27 +15,31 @@
*/ */
package org.thingsboard.server.dao.entity; package org.thingsboard.server.dao.entity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import javax.annotation.PostConstruct;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Service @Service
@RequiredArgsConstructor
@Slf4j
public class DefaultEntityServiceRegistry implements EntityServiceRegistry { public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final Map<EntityType, EntityDaoService> entityDaoServicesMap; private final Map<EntityType, EntityDaoService> entityDaoServicesMap = new HashMap<>();
public DefaultEntityServiceRegistry(ApplicationContext applicationContext) { @EventListener(ContextRefreshedEvent.class)
this.applicationContext = applicationContext; @Order(Ordered.HIGHEST_PRECEDENCE)
this.entityDaoServicesMap = new HashMap<>();
}
@PostConstruct
public void init() { public void init() {
log.debug("Initializing EntityServiceRegistry on ContextRefreshedEvent");
applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> { applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> {
EntityType entityType = entityDaoService.getEntityType(); EntityType entityType = entityDaoService.getEntityType();
entityDaoServicesMap.put(entityType, entityDaoService); entityDaoServicesMap.put(entityType, entityDaoService);
@ -43,6 +47,7 @@ public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
entityDaoServicesMap.put(EntityType.RULE_NODE, entityDaoService); entityDaoServicesMap.put(EntityType.RULE_NODE, entityDaoService);
} }
}); });
log.debug("Initialized EntityServiceRegistry total [{}] entries", entityDaoServicesMap.size());
} }
@Override @Override

3
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -680,7 +680,6 @@ public class ModelConstants {
* */ * */
public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target"; public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target";
public static final String NOTIFICATION_TARGET_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration"; public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_TABLE_NAME = "notification"; public static final String NOTIFICATION_TABLE_NAME = "notification";
@ -688,7 +687,7 @@ public class ModelConstants {
public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id";
public static final String NOTIFICATION_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject"; public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject";
public static final String NOTIFICATION_TEXT_PROPERTY = "text"; public static final String NOTIFICATION_TEXT_PROPERTY = "body";
public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config";
public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_STATUS_PROPERTY = "status";

39
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java

@ -30,10 +30,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.notification.cache.NotificationRequestCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRequestCacheValue;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.util.List; import java.util.List;
@ -42,7 +39,7 @@ import java.util.Optional;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationRequestService extends AbstractCachedEntityService<NotificationRequestCacheKey, NotificationRequestCacheValue, NotificationRequest> implements NotificationRequestService, EntityDaoService { public class DefaultNotificationRequestService implements NotificationRequestService, EntityDaoService {
private final NotificationRequestDao notificationRequestDao; private final NotificationRequestDao notificationRequestDao;
@ -51,14 +48,7 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
@Override @Override
public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId);
try { return notificationRequestDao.save(tenantId, notificationRequest);
notificationRequest = notificationRequestDao.save(tenantId, notificationRequest);
publishEvictEvent(notificationRequest);
} catch (Exception e) {
handleEvictEvent(notificationRequest);
throw e;
}
return notificationRequest;
} }
@Override @Override
@ -88,21 +78,13 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
@Override @Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) {
NotificationRequestCacheKey cacheKey = NotificationRequestCacheKey.builder() return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId);
.originatorEntityId(originatorEntityId)
.ruleId(ruleId)
.build();
return cache.getAndPutInTransaction(cacheKey, () -> NotificationRequestCacheValue.builder()
.notificationRequests(notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId))
.build(), false)
.getNotificationRequests();
} }
// ON DELETE CASCADE is used: notifications for request are deleted as well // ON DELETE CASCADE is used: notifications for request are deleted as well
@Override @Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId) {
publishEvictEvent(notificationRequest); notificationRequestDao.removeById(tenantId, requestId.getId());
notificationRequestDao.removeById(tenantId, notificationRequest.getUuidId());
} }
@Override @Override
@ -120,17 +102,6 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
notificationRequestDao.removeByTenantId(tenantId); notificationRequestDao.removeByTenantId(tenantId);
} }
@Override
public void handleEvictEvent(NotificationRequest notificationRequest) {
if (notificationRequest.getRuleId() == null) return;
NotificationRequestCacheKey cacheKey = NotificationRequestCacheKey.builder()
.originatorEntityId(notificationRequest.getOriginatorEntityId())
.ruleId(notificationRequest.getRuleId())
.build();
cache.evict(cacheKey);
}
@Override @Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) { public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationRequestById(tenantId, new NotificationRequestId(entityId.getId()))); return Optional.ofNullable(findNotificationRequestById(tenantId, new NotificationRequestId(entityId.getId())));

6
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java

@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.NotificationId; 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.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
@ -77,11 +76,6 @@ public class DefaultNotificationService implements NotificationService {
return notificationDao.countUnreadByRecipientId(tenantId, recipientId); return notificationDao.countUnreadByRecipientId(tenantId, recipientId);
} }
@Override
public void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) {
notificationDao.updateStatusesByRequestId(tenantId, requestId, status);
}
@Override @Override
public boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) { public boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
return notificationDao.deleteByIdAndRecipientId(tenantId, recipientId, notificationId); return notificationDao.deleteByIdAndRecipientId(tenantId, recipientId, notificationId);

2
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -121,7 +121,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
if (!tenantId.equals(TenantId.SYS_TENANT_ID)) { if (!tenantId.equals(TenantId.SYS_TENANT_ID)) {
return userService.findUsersByTenantId(tenantId, pageLink); return userService.findUsersByTenantId(tenantId, pageLink);
} else { } else {
return userService.findUsers(TenantId.SYS_TENANT_ID, pageLink); return userService.findAllUsers(TenantId.SYS_TENANT_ID, pageLink);
} }
} }
case ORIGINATOR_ENTITY_OWNER_USERS: { case ORIGINATOR_ENTITY_OWNER_USERS: {

2
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java

@ -37,8 +37,6 @@ public interface NotificationDao extends Dao<Notification> {
PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink);
void updateStatusesByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId); boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId);
int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status); int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status);

32
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCaffeineCache.java

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service
public class NotificationRequestCaffeineCache extends CaffeineTbTransactionalCache<NotificationRequestCacheKey, NotificationRequestCacheValue> {
public NotificationRequestCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.NOTIFICATION_REQUESTS_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestRedisCache.java

@ -1,35 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service
public class NotificationRequestRedisCache extends RedisTbTransactionalCache<NotificationRequestCacheKey, NotificationRequestCacheValue> {
public NotificationRequestRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) {
super(CacheConstants.NOTIFICATION_REQUESTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

19
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -17,13 +17,15 @@ package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao; import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -32,6 +34,8 @@ import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.capitalize;
@Slf4j @Slf4j
public abstract class DataValidator<D extends BaseData<?>> { public abstract class DataValidator<D extends BaseData<?>> {
private static final Pattern EMAIL_PATTERN = private static final Pattern EMAIL_PATTERN =
@ -42,6 +46,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
private static final String NAME = "name"; private static final String NAME = "name";
private static final String TOPIC = "topic"; private static final String TOPIC = "topic";
@Autowired @Lazy
private ApiLimitService apiLimitService;
// Returns old instance of the same object that is fetched during validation. // Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) { public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try { try {
@ -97,15 +104,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
} }
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId, protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
TenantEntityDao tenantEntityDao,
long maxEntities,
EntityType entityType) { EntityType entityType) {
if (maxEntities > 0) { if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId); throw new DataValidationException(String.format("%ss limit reached", capitalize(entityType.name().toLowerCase().replaceAll("_", " "))));
if (currentEntitiesCount >= maxEntities) {
throw new DataValidationException(String.format("Can't create more then %d %ss!",
maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
}
} }
} }

11
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -24,13 +24,11 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.BaseAssetService; import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -48,17 +46,10 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Asset asset) { protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
long maxAssets = profileConfiguration.getMaxAssets(); validateNumberOfEntitiesPerTenant(tenantId, EntityType.ASSET);
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
} }
} }

13
dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

@ -16,18 +16,15 @@
package org.thingsboard.server.dao.service.validator; package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.customer.CustomerServiceImpl; import org.thingsboard.server.dao.customer.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional; import java.util.Optional;
@ -41,17 +38,9 @@ public class CustomerDataValidator extends DataValidator<Customer> {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
protected void validateCreate(TenantId tenantId, Customer customer) { protected void validateCreate(TenantId tenantId, Customer customer) {
DefaultTenantProfileConfiguration profileConfiguration = validateNumberOfEntitiesPerTenant(tenantId, EntityType.CUSTOMER);
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxCustomers = profileConfiguration.getMaxCustomers();
validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> { c -> {
throw new DataValidationException("Customer with such title already exists!"); throw new DataValidationException("Customer with such title already exists!");

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

Loading…
Cancel
Save