Browse Source

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

pull/8212/head
YevhenBondarenko 3 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);
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 (
id UUID NOT NULL CONSTRAINT notification_target_pkey PRIMARY KEY,
created_time BIGINT NOT NULL,
@ -122,7 +126,7 @@ CREATE TABLE IF NOT EXISTS notification_rule (
additional_config VARCHAR(255),
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 (
id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY,
@ -139,9 +143,12 @@ CREATE TABLE IF NOT EXISTS notification_request (
status VARCHAR(32),
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_rule_id_originator_entity_id ON notification_request(rule_id, originator_entity_id);
CREATE INDEX IF NOT EXISTS idx_notification_request_status ON notification_request(status);
CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_user_created_time ON notification_request(tenant_id, created_time DESC)
WHERE originator_entity_type = 'USER';
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 (
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,
type VARCHAR(50) NOT NULL,
subject VARCHAR(255),
text VARCHAR(1000) NOT NULL,
body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000),
info VARCHAR(1000),
status VARCHAR(32)
) 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_recipient_id_status_created_time ON notification(recipient_id, status, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
-- NOTIFICATIONS END
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
);
-- ALARM COMMENTS END
-- ALARM INFO VIEW
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;
END IF;
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
$$;

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);
tbMsg.getCallback().onSuccess();
}
systemContext.getNotificationRuleProcessingService().process(tenantId, tbMsg);
}
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;
@PostMapping("/rule")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule) throws Exception {
notificationRule.setTenantId(getTenantId());
checkEntity(notificationRule.getId(), notificationRule, NOTIFICATION);
@ -63,14 +63,14 @@ public class NotificationRuleController extends BaseController {
}
@GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
NotificationRuleId notificationRuleId = new NotificationRuleId(id);
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ);
}
@GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData<NotificationRuleInfo> getNotificationRules(@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@ -83,7 +83,7 @@ public class NotificationRuleController extends BaseController {
}
@DeleteMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationRule(@PathVariable UUID id,
@AuthenticationPrincipal SecurityUser user) throws Exception {
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.id.CustomerId;
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.NotificationTargetConfig;
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.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.UsersFilterType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -159,11 +160,8 @@ public class NotificationTargetController extends BaseController {
// generic permission for users
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter();
if (usersFilter.getType() == UsersFilterType.USER_LIST) {
PageDataIterable<User> recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, targetConfig, pageLink);
}, 200);
for (User recipient : recipients) {
checkEntity(user, recipient, Operation.READ);
for (UUID recipientId : ((UserListFilter) usersFilter).getUsersIds()) {
checkUserId(new UserId(recipientId), Operation.READ);
}
} else if (usersFilter.getType() == UsersFilterType.CUSTOMER_USERS) {
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.alarm.Alarm;
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.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -221,7 +222,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
}
@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);
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.alarm.Alarm;
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.edge.Edge;
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,
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);

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

@ -22,12 +22,12 @@ import org.thingsboard.common.util.AbstractListeningExecutor;
@Component
public class NotificationExecutorService extends AbstractListeningExecutor {
@Value("${notification_system.thread_pool_size}")
private int notificationSystemExecutorThreadPoolSize;
@Value("${notification_system.thread_pool_size:30}")
private int threadPoolSize;
@Override
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.template.DeliveryMethodNotificationTemplate;
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.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.ServiceType;
@ -82,7 +82,7 @@ import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@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 NotificationRequestService notificationRequestService;
@ -93,6 +93,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider;
private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@ -244,7 +245,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
@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();
Notification notification = Notification.builder()
.requestId(request.getId())
@ -264,8 +265,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
NotificationUpdate update = NotificationUpdate.builder()
.created(true)
.notification(notification)
.updateType(ComponentLifecycleEvent.CREATED)
.build();
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);
NotificationUpdate update = NotificationUpdate.builder()
.created(true)
.notification(notification)
.updateType(ComponentLifecycleEvent.CREATED)
.build();
onNotificationUpdate(tenantId, recipientId, update);
}
@ -294,9 +295,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (updated) {
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId)
.updatedStatus(NotificationStatus.READ)
.updateType(ComponentLifecycleEvent.UPDATED)
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
}
@ -308,9 +309,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (updatedCount > 0) {
log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.allNotifications(true)
.updatedStatus(NotificationStatus.READ)
.updateType(ComponentLifecycleEvent.UPDATED)
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
}
@ -322,43 +323,28 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId);
if (deleted) {
NotificationUpdate update = NotificationUpdate.builder()
.deleted(true)
.notification(notification)
.updateType(ComponentLifecycleEvent.DELETED)
.build();
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
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) {
log.debug("Deleting notification request {}", 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()) {
// TODO: no need to send request update for other than PLATFORM_USERS target type
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId)
.deleted(true)
.build());
} else if (notificationRequest.isScheduled()) {
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
}
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
}
private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) {
@ -397,7 +383,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.PUSH;
return NotificationDeliveryMethod.WEB;
}
@Override
@ -406,9 +392,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
@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.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;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
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.alarm.Alarm;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
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.msg.TbMsg;
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.NotificationRuleService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
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.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings("rawtypes")
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService {
private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService;
@Autowired @Lazy
private NotificationCenter notificationCenter;
private Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors;
private final NotificationExecutorService notificationExecutor;
private final DbCallbackExecutorService dbCallbackExecutor;
private final Map<String, NotificationRuleTriggerType> msgTypeToTriggerType = Map.of(
DataConstants.INACTIVITY_EVENT, NotificationRuleTriggerType.DEVICE_INACTIVITY,
DataConstants.ENTITY_CREATED, NotificationRuleTriggerType.ENTITY_ACTION,
DataConstants.ENTITY_UPDATED, NotificationRuleTriggerType.ENTITY_ACTION,
DataConstants.ENTITY_DELETED, NotificationRuleTriggerType.ENTITY_ACTION,
DataConstants.COMMENT_CREATED, NotificationRuleTriggerType.ALARM_COMMENT,
DataConstants.COMMENT_UPDATED, NotificationRuleTriggerType.ALARM_COMMENT
);
private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
private final Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
@Override
public void process(TenantId tenantId, TbMsg ruleEngineMsg) {
String msgType = ruleEngineMsg.getType();
NotificationRuleTriggerType triggerType = msgTypeToTriggerType.get(msgType);
NotificationRuleTriggerType triggerType = ruleEngineMsgTypeToTriggerType.get(msgType);
if (triggerType == null) {
return;
}
processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg);
}
@Override
public void process(TenantId tenantId, Alarm alarm, boolean deleted) {
AlarmTriggerObject triggerObject = AlarmTriggerObject.builder()
.alarm(alarm)
.deleted(deleted)
.build();
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), triggerObject);
public void process(TenantId tenantId, AlarmApiCallResult alarmUpdate) {
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarmUpdate.getAlarm().getId(), alarmUpdate);
}
@Override
@ -112,73 +102,78 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
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) {
ListenableFuture<List<NotificationRule>> rulesFuture = dbCallbackExecutor.submit(() -> {
return notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
});
DonAsynchron.withCallback(rulesFuture, rules -> {
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
try {
processNotificationRule(rule, originatorEntityId, triggerObject);
});
}
}, e -> {
log.error("Failed to find notification rules by trigger type {}", triggerType, e);
});
} catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), triggerObject, e);
}
});
}
}
private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
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);
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);
});
}
}
if (notificationRequests.isEmpty()) {
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;
}
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
notificationExecutor.submit(() -> {
try {
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);
}
if (matchesFilter(triggerObject, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(triggerObject, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
});
});
}
}
private boolean matchesFilter(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) {
@ -208,7 +203,14 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.ruleId(rule.getId())
.originatorEntityId(originatorEntityId)
.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)
@ -220,7 +222,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
dbCallbackExecutor.submit(() -> {
notificationExecutor.submit(() -> {
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
@ -230,8 +232,15 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
this.triggerProcessors = processors.stream()
.collect(Collectors.toMap(NotificationRuleTriggerProcessor::getTriggerType, p -> p));
processors.forEach(processor -> {
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;
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.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
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 {
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,
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.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.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.NotificationInfo;
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.msg.TbMsg;
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, AlarmCommentNotificationRuleTriggerConfig> {
public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmCommentNotificationRuleTriggerConfig> {
@Override
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
public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
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()
.comment(comment.getComment().get("text").asText())
.alarmType(alarm.getType())
.alarmId(comment.getAlarmId().getId())
.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();
}
@ -49,4 +76,9 @@ public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProc
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;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
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.NotificationInfo;
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.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
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTriggerObject, AlarmNotificationRuleTriggerConfig> {
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmApiCallResult, AlarmNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm();
return (CollectionUtils.isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(CollectionUtils.isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity()));
public boolean matchesFilter(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
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
public boolean matchesClearRule(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
if (triggerObject.isDeleted()) {
public boolean matchesClearRule(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = alarmUpdate.getAlarm();
if (!typeMatches(alarm, triggerConfig)) {
return false;
}
if (alarmUpdate.isDeleted()) {
return true;
}
Alarm alarm = triggerObject.getAlarm();
ClearRule clearRule = triggerConfig.getClearRule();
if (clearRule != null) {
if (clearRule.getAlarmStatus() != null) {
return clearRule.getAlarmStatus().equals(alarm.getStatus());
if (isNotEmpty(clearRule.getAlarmStatuses())) {
return AlarmStatusFilter.from(clearRule.getAlarmStatuses()).matches(alarm);
}
}
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
public NotificationInfo constructNotificationInfo(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) {
Alarm alarm = triggerObject.getAlarm();
public NotificationInfo constructNotificationInfo(AlarmApiCallResult alarmUpdate, AlarmNotificationRuleTriggerConfig triggerConfig) {
// TODO: readable action
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder()
.alarmId(alarm.getUuidId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.alarmCustomerId(alarm.getCustomerId())
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
.alarmOriginatorName(alarmInfo.getOriginatorName())
.alarmSeverity(alarmInfo.getSeverity())
.alarmStatus(alarmInfo.getStatus())
.alarmCustomerId(alarmInfo.getCustomerId())
.build();
}
@ -70,11 +109,4 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
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 org.apache.commons.collections.CollectionUtils;
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.id.DeviceId;
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.service.profile.TbDeviceProfileCache;
import java.util.Set;
@Service
@RequiredArgsConstructor
public class DeviceInactivityTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, DeviceInactivityNotificationRuleTriggerConfig> {
public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceInactivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache;
@ -53,6 +56,7 @@ public class DeviceInactivityTriggerProcessor implements NotificationRuleTrigger
.deviceId(ruleEngineMsg.getOriginator().getId())
.deviceName(ruleEngineMsg.getMetaData().getValue("deviceName"))
.deviceType(ruleEngineMsg.getMetaData().getValue("deviceType"))
.deviceLabel(ruleEngineMsg.getMetaData().getValue("deviceLabel"))
.deviceCustomerId(ruleEngineMsg.getCustomerId())
.build();
}
@ -62,4 +66,9 @@ public class DeviceInactivityTriggerProcessor implements NotificationRuleTrigger
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.msg.TbMsg;
import java.util.Set;
import java.util.UUID;
@Service
public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, EntityActionNotificationRuleTriggerConfig> {
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
@Override
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_DELETED) ? ActionType.DELETED : null;
return EntityActionNotificationInfo.builder()
.entityType(entityId.getEntityType())
.entityId(entityId.getId())
.entityId(entityId)
.entityName(ruleEngineMsg.getMetaData().getValue("entityName"))
.actionType(actionType)
.originatorUserId(UUID.fromString(ruleEngineMsg.getMetaData().getValue("userId")))
@ -74,4 +74,9 @@ public class EntityActionTriggerProcessor implements NotificationRuleTriggerProc
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,
PartitionService partitionService,
ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
Optional<JwtSettingsService> jwtSettingsService,
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.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
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,
TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
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.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;
@ -482,7 +481,6 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage());
actorContext.tell(msg);
notificationRuleProcessingService.process(tenantId, tbMsg);
}
@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.queue.ServiceType;
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.dao.tenant.TbTenantProfileCache;
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 PartitionService partitionService;
protected final ApplicationEventPublisher eventPublisher;
protected final NotificationRuleProcessingService notificationRuleProcessingService;
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
protected final Optional<JwtSettingsService> jwtSettingsService;
@ -88,7 +86,6 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache,
TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer, Optional<JwtSettingsService> jwtSettingsService) {
this.actorContext = actorContext;
this.encodingService = encodingService;
@ -98,7 +95,6 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
this.apiUsageStateService = apiUsageStateService;
this.partitionService = partitionService;
this.eventPublisher = eventPublisher;
this.notificationRuleProcessingService = notificationRuleProcessingService;
this.nfConsumer = nfConsumer;
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.context.annotation.Lazy;
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.User;
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(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "type"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
private final TenantService tenantService;
@ -329,6 +330,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType());
stateData.setMetaData(md);
callback.onSuccess();
@ -581,6 +583,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build();
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", device.getName());
md.putValue("deviceLabel", device.getLabel());
md.putValue("deviceType", device.getType());
DeviceStateData deviceStateData = DeviceStateData.builder()
.customerId(device.getCustomerId())
@ -660,6 +663,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
.build();
TbMsgMetaData md = new TbMsgMetaData();
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", ""));
return DeviceStateData.builder()
.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.common.data.DataConstants;
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.id.DeviceId;
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.TbCallback;
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.timeseries.TimeseriesService;
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.provider.TbQueueProducerProvider;
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.DeviceStateService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
@ -100,7 +97,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private final TbLocalSubscriptionService localSubscriptionService;
private final DeviceStateService deviceStateService;
private final TbClusterService clusterService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>();
@ -296,7 +292,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs() || alarm.getAssignTs() >= s.getTs(),
alarm, false
);
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess();
}
@ -313,7 +308,6 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm.getCreatedTime() >= s.getTs(),
alarm, true
);
notificationRuleProcessingService.process(tenantId, alarm, true);
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.context.annotation.Lazy;
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.server.cluster.TbClusterService;
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.service.sync.vc.data.ClearRepositoryGitRequest;
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.EntityContentGitRequest;
import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest;
@ -78,11 +77,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
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.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
@ -68,6 +69,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
@Override
protected String getExecutorPrefix() {
@ -232,6 +234,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
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);
});
}
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.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -53,6 +55,9 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}")
private boolean updatesEnabled;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture checkUpdatesFuture = null;
@ -121,12 +126,16 @@ public class DefaultUpdateService implements UpdateService {
request.put(PLATFORM_PARAM, platform);
request.put(VERSION_PARAM, version);
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(
response.get("message").asText(),
response.get("updateAvailable").asBoolean(),
version
);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(updateMessage);
}
} catch (Exception e) {
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 {
cmdHandler.handle(sessionRef, cmd);
} 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);
Notification notification = update.getNotification();
UUID notificationId = update.getNotificationId();
switch (update.getUpdateType()) {
case CREATED: {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet();
if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) {
Set<UUID> beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit())
.map(IdBased::getUuidId).collect(Collectors.toSet());
beyondLimit.forEach(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;
if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet();
if (subscription.getLatestUnreadNotifications().size() > subscription.getLimit()) {
Set<UUID> beyondLimit = subscription.getSortedNotifications().stream().skip(subscription.getLimit())
.map(IdBased::getUuidId).collect(Collectors.toSet());
beyondLimit.forEach(id -> subscription.getLatestUnreadNotifications().remove(id));
}
case DELETED: {
if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
} else if (update.isUpdated()) {
if (update.getNewStatus() == NotificationStatus.READ) {
if (update.isAllNotifications() || subscription.getLatestUnreadNotifications().containsKey(notificationId)) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
} else if (notification.getStatus() != NotificationStatus.READ) {
} else {
subscription.getTotalUnreadCounter().decrementAndGet();
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) {
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());
}
@ -191,47 +184,33 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationUpdate(NotificationsCountSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification();
switch (update.getUpdateType()) {
case CREATED: {
subscription.getUnreadCounter().incrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
break;
}
case UPDATED: {
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) {
if (update.isCreated()) {
subscription.getUnreadCounter().incrementAndGet();
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
} else if (update.isUpdated()) {
if (update.getNewStatus() == NotificationStatus.READ) {
if (update.isAllNotifications()) {
fetchUnreadNotificationsCount(subscription);
} else {
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) {
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());
}
private void sendUpdate(String sessionId, CmdUpdate update) {
log.trace("[{}, cmdId: {}] Sending WS update: {}", sessionId, update.getCmdId(), update);
wsService.sendWsMsg(sessionId, update);
}
@Override
public void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd) {
SecurityUser securityCtx = sessionRef.getSecurityCtx();
@ -253,4 +232,9 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
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.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@Data
@NoArgsConstructor
@ -28,6 +27,5 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@Builder
public class NotificationRequestUpdate {
private NotificationRequestId notificationRequestId;
private NotificationInfo notificationInfo;
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.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.UUID;
@ -33,12 +32,15 @@ import java.util.UUID;
public class NotificationUpdate {
private NotificationId notificationId;
private boolean created;
private Notification notification;
boolean allNotifications;
private boolean updated;
private NotificationStatus newStatus;
private boolean allNotifications;
private NotificationStatus updatedStatus;
private ComponentLifecycleEvent updateType;
private boolean deleted;
public UUID getNotificationId() {
return notificationId != null ? notificationId.getId() :

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

@ -440,9 +440,6 @@ cache:
notificationRules:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
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:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
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) {
DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
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) {

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.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(device.getId(), FeatureType.ATTRIBUTES, 1);
edgeImitator.expectResponsesAmount(1);
@ -690,7 +691,7 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
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),
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.NotificationTemplate;
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.page.PageData;
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) {
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);
return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec);
@ -137,8 +137,8 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) {
case PUSH: {
PushDeliveryMethodNotificationTemplate template = new PushDeliveryMethodNotificationTemplate();
case WEB: {
WebDeliveryMethodNotificationTemplate template = new WebDeliveryMethodNotificationTemplate();
template.setSubject(subject);
deliveryMethodNotificationTemplate = template;
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.NotificationTemplate;
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.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
@ -209,7 +209,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
int notificationsCount = 20;
wsClient.registerWaitForUpdate(notificationsCount);
for (int i = 1; i <= notificationsCount; i++) {
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.PUSH);
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
}
wsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount);
@ -266,32 +266,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
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
public void testNotificationUpdatesForSeveralUsers() throws Exception {
int usersCount = 150;
@ -319,7 +293,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate());
NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${recipientEmail}", 0,
NotificationDeliveryMethod.PUSH);
NotificationDeliveryMethod.WEB);
await().atMost(10, TimeUnit.SECONDS)
.pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> {
@ -342,7 +316,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
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());
deleteNotificationRequest(notificationRequest.getId());
@ -393,17 +367,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
String requestorEmail = TENANT_ADMIN_EMAIL;
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}");
HashMap<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>();
templateConfig.setDeliveryMethodsTemplates(templates);
notificationTemplate.setConfiguration(templateConfig);
PushDeliveryMethodNotificationTemplate pushNotificationTemplate = new PushDeliveryMethodNotificationTemplate();
pushNotificationTemplate.setEnabled(true);
// using default message for push
pushNotificationTemplate.setSubject("Subject for PUSH: ${recipientEmail}");
templates.put(NotificationDeliveryMethod.PUSH, pushNotificationTemplate);
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
webNotificationTemplate.setEnabled(true);
// using default message for web
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
SmsDeliveryMethodNotificationTemplate smsNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
smsNotificationTemplate.setEnabled(true);
@ -435,19 +409,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
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 -> {
assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH")
.startsWith("Default message for SMS and WEB")
.endsWith(requestorEmail);
assertThat(template.getSubject())
.startsWith("Subject for PUSH")
.startsWith("Subject for WEB")
.endsWith(requestorEmail);
});
assertThat(processedTemplates.get(NotificationDeliveryMethod.SMS)).asInstanceOf(type(SmsDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH")
.startsWith("Default message for SMS and WEB")
.endsWith(requestorEmail);
});
assertThat(processedTemplates.get(NotificationDeliveryMethod.EMAIL)).asInstanceOf(type(EmailDeliveryMethodNotificationTemplate.class))
@ -469,7 +443,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{
NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL
NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL
};
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods);
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
@ -489,15 +463,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
wsClient.registerWaitForUpdate();
NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Test :)",
NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS);
NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.SMS);
wsClient.waitForUpdate();
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1);
assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1);
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SMS)).size().isOne();
}
@ -526,7 +499,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTargetId notificationTargetId = notificationTarget.getId();
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);
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.boot.test.mock.mockito.SpyBean;
import org.springframework.data.util.Pair;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode;
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.User;
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.AlarmStatus;
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.NotificationRuleInfo;
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.NotificationRuleTriggerType;
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;
@DaoSqlTest
@TestPropertySource(properties = {
"js.evaluator=local"
})
public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@SpyBean
@ -110,10 +116,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${actionType}: ${entityType} [${entityId}]";
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.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.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
@ -165,16 +171,17 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " +
"severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}";
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.setName("Push-notification on any alarm");
notificationRule.setName("Web notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
@ -234,30 +241,28 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
});
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;
AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL;
clients.values().forEach(wsClient -> {
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 + ", " +
"severity: " + expectedSeverity + ", deviceId: " + device.getId());
assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity);
wsClient.close();
});
// TODO: test severity changes
}
@Test
public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception {
String notificationSubject = "${alarmSeverity} alarm '${alarmType}' is ${alarmStatus}";
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.setName("Push-notification on any alarm");
notificationRule.setName("Web notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
@ -268,9 +273,10 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(Set.of(alarmType));
triggerConfig.setAlarmSeverities(null);
triggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED));
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = new AlarmNotificationRuleTriggerConfig.ClearRule();
clearRule.setAlarmStatus(AlarmStatus.CLEARED_UNACK);
clearRule.setAlarmStatuses(Set.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.UNACK));
triggerConfig.setClearRule(clearRule);
notificationRule.setTriggerConfig(triggerConfig);
@ -308,9 +314,9 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(scheduledNotificationRequest).extracting(NotificationRequest::getInfo).isEqualTo(notification.getInfo());
getWsClient().registerWaitForUpdate();
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), null, System.currentTimeMillis());
alarmSubscriptionService.clearAlarm(tenantId, alarm.getId(), System.currentTimeMillis(), null);
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getNotifications().iterator().next();
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("CRITICAL alarm '" + alarmType + "' is CLEARED_UNACK");
assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty();
@ -320,7 +326,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
public void testNotificationRuleProcessing_ruleEngineComponentLifecycleEvent_ruleNodeStartError() {
String subject = "Rule Node '${componentName}' in Rule Chain '${ruleChainName}' failed to start";
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();
rule.setName("Rule node start-up failures in my rule chain");
@ -360,7 +366,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test
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);
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
public void testTemplatesSearch() throws Exception {
NotificationTemplate alarmNotificationTemplate = createNotificationTemplate(NotificationType.ALARM, "Alarm", "Alarm", NotificationDeliveryMethod.PUSH);
NotificationTemplate generalNotificationTemplate = createNotificationTemplate(NotificationType.GENERAL, "General", "General", NotificationDeliveryMethod.PUSH);
NotificationTemplate entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.PUSH);
NotificationTemplate alarmNotificationTemplate = createNotificationTemplate(NotificationType.ALARM, "Alarm", "Alarm", NotificationDeliveryMethod.WEB);
NotificationTemplate generalNotificationTemplate = createNotificationTemplate(NotificationType.GENERAL, "General", "General", NotificationDeliveryMethod.WEB);
NotificationTemplate entityActionNotificationTemplate = createNotificationTemplate(NotificationType.ENTITY_ACTION, "Entity action", "Entity action", NotificationDeliveryMethod.WEB);
assertThat(findTemplates(NotificationType.ALARM)).extracting(IdBased::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.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.ResourceType;
@ -46,6 +44,7 @@ import java.util.Base64;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
@ -116,10 +115,6 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
@SuppressWarnings("deprecation")
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception {
loginSysAdmin();
@ -138,9 +133,9 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try {
thrown.expect(DataValidationException.class);
thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit));
createResource("test1", 1 + DEFAULT_FILE_NAME);
assertThatThrownBy(() -> createResource("test1", 1 + DEFAULT_FILE_NAME))
.isInstanceOf(DataValidationException.class)
.hasMessageContaining("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit);
} finally {
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build());
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());
client.publishAndWait(attrPubTopic, CLIENT_ATTRIBUTES_PAYLOAD.getBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
//RequestAttributes does not make any subscriptions in device actor
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
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());
client.publishAndWait(attrPubTopic, getAttributesProtoPayloadBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
//RequestAttributes does not make any subscriptions in device actor
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1"));
@ -442,6 +446,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertThat(update).as("ws update received").isNotBlank();
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);
client.setCallback(clientAttributesCallback);
@ -495,6 +500,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertThat(update).as("ws update received").isNotBlank();
client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE);
awaitForDeviceActorToReceiveSubscription(device.getId(), FeatureType.ATTRIBUTES, 1);
MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
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);
client.setCallback(callback);
client.subscribeAndWait(GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE);
subscribeAndCheckSubscription(client, GATEWAY_RPC_TOPIC, savedDevice.getId(), FeatureType.RPC);
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.server.common.data.device.profile.MqttTopics;
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.mqttv5.AbstractMqttV5Test;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestCallback;
@ -104,6 +105,7 @@ public abstract class AbstractAttributesMqttV5Test extends AbstractMqttV5Test {
MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback();
client.setCallback(onUpdateCallback);
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());
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.junit.Assert;
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.mqttv5.MqttV5TestCallback;
import org.thingsboard.server.transport.mqtt.mqttv5.MqttV5TestClient;
@ -101,6 +102,7 @@ public abstract class AbstractMqttV5ClientConnectionTest extends AbstractMqttInt
MqttV5TestCallback onUpdateCallback = new MqttV5TestCallback();
client.setCallback(onUpdateCallback);
client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
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.junit.Assert;
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.mqttv5.MqttV5TestClient;
@ -34,6 +35,7 @@ public abstract class AbstractMqttV5ClientSubscriptionTest extends AbstractMqttI
client.connectAndWait(accessToken);
IMqttToken subscriptionResult = client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
MqttWireMessage response = subscriptionResult.getResponse();
@ -52,6 +54,7 @@ public abstract class AbstractMqttV5ClientSubscriptionTest extends AbstractMqttI
client.connectAndWait(accessToken);
IMqttToken iMqttToken = client.subscribeAndWait("wrong/topic/+", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 0);
Assert.assertEquals(MESSAGE_TYPE_SUBACK,iMqttToken.getResponse().getType());
MqttSubAck subAck = (MqttSubAck) iMqttToken.getResponse();
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.junit.Assert;
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.mqttv5.MqttV5TestClient;
@ -34,6 +35,7 @@ public abstract class AbstractMqttV5ClientUnsubscribeTest extends AbstractMqttIn
client.connectAndWait(accessToken);
client.subscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedDevice.getId(), FeatureType.ATTRIBUTES, 1);
IMqttToken unsubscribeResult = client.unsubscribeAndWait(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
MqttWireMessage response = unsubscribeResult.getResponse();
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.thingsboard.common.util.JacksonUtil;
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.MqttV5TestCallback;
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"));
client.setCallback(callback);
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 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();
client.connectAndWait(accessToken);
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"));
client.setCallback(callback);
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 org.junit.Assert;
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.util.sparkplug.MetricDataType;
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 + "}";
Assert.assertTrue("Connection node is failed", client.isConnected());
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());
await(alias + SparkplugMessageType.NBIRTH.name())
.atMost(40, TimeUnit.SECONDS)
@ -75,6 +77,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Boolean <-> String
boolean expectedValue = true;
@ -138,6 +141,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Long <-> String
String valueStr = "123";
@ -189,6 +193,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Float <-> String
String valueStr = "123.345";
@ -240,6 +245,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// Double <-> String
String valueStr = "123345456";
@ -291,6 +297,7 @@ public abstract class AbstractMqttV5ClientSparkplugAttributesTest extends Abstra
connectionWithNBirth(metricDataType, metricKey, metricValue);
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.ATTRIBUTES, 1);
// String <-> Long
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.Test;
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.util.sparkplug.SparkplugMessageType;
@ -45,6 +46,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected());
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 actual = sendRPCSparkplug(NCMD.name(), sparkplugRpcRequest, savedGateway);
await(alias + SparkplugMessageType.NCMD.name())
@ -79,6 +81,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.RPC, 1);
String invalidateTypeMessageName = "RCMD";
String expected = "{\"result\":\"" + INVALID_ARGUMENTS + "\",\"error\":\"Failed to convert device RPC command to MQTT msg: " +
invalidateTypeMessageName + "{\\\"metricName\\\":\\\"" + metricBirthName_Int32 + "\\\",\\\"value\\\":" + metricBirthValue_Int32 + "}\"}";
@ -92,6 +95,7 @@ public abstract class AbstractMqttV5RpcSparkplugTest extends AbstractMqttV5Clie
connectionWithNBirth(metricBirthDataType_Int32, metricBirthName_Int32, nextInt32());
Assert.assertTrue("Connection node is failed", client.isConnected());
client.subscribeAndWait(NAMESPACE + "/" + groupId + "/" + NCMD.name() + "/" + edgeNode + "/#", MqttQoS.AT_MOST_ONCE);
awaitForDeviceActorToReceiveSubscription(savedGateway.getId(), FeatureType.RPC, 1);
String metricNameBad = metricBirthName_Int32 + "_Bad";
String sparkplugRpcRequestBad = "{\"metricName\":\"" + metricNameBad + "\",\"value\":" + metricBirthValue_Int32 + "}";
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 modified;
private final boolean cleared;
private final boolean deleted;
private final AlarmInfo alarm;
private final Alarm old;
private final List<EntityId> propagatedEntitiesList;
@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.created = created;
this.modified = modified;
this.cleared = cleared;
this.deleted = deleted;
this.alarm = alarm;
this.old = old;
this.propagatedEntitiesList = propagatedEntitiesList;
@ -52,6 +54,7 @@ public class AlarmApiCallResult {
this.created = other.created;
this.modified = other.modified;
this.cleared = other.cleared;
this.deleted = other.deleted;
this.alarm = other.alarm;
this.old = other.old;
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() {
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);
void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId);
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);
void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
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> findUsers(TenantId tenantId, PageLink pageLink);
PageData<User> findAllUsers(TenantId tenantId, PageLink pageLink);
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 DEVICE_PROFILE_CACHE = "deviceProfiles";
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 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;
import java.util.List;
import java.util.Collection;
import java.util.Optional;
public class AlarmStatusFilter {
@ -94,19 +94,19 @@ public class AlarmStatusFilter {
}
public static AlarmStatusFilter fromList(List<AlarmSearchStatus> list) {
if (list == null || list.isEmpty() || list.contains(AlarmSearchStatus.ANY)) {
public static AlarmStatusFilter from(Collection<AlarmSearchStatus> statuses) {
if (statuses == null || statuses.isEmpty() || statuses.contains(AlarmSearchStatus.ANY)) {
return EMPTY;
}
boolean clearFilter = list.contains(AlarmSearchStatus.CLEARED);
boolean activeFilter = list.contains(AlarmSearchStatus.ACTIVE);
boolean clearFilter = statuses.contains(AlarmSearchStatus.CLEARED);
boolean activeFilter = statuses.contains(AlarmSearchStatus.ACTIVE);
Optional<Boolean> clear = Optional.empty();
if (clearFilter && !activeFilter || !clearFilter && activeFilter) {
clear = Optional.of(clearFilter);
}
boolean ackFilter = list.contains(AlarmSearchStatus.ACK);
boolean unackFilter = list.contains(AlarmSearchStatus.UNACK);
boolean ackFilter = statuses.contains(AlarmSearchStatus.ACK);
boolean unackFilter = statuses.contains(AlarmSearchStatus.UNACK);
Optional<Boolean> ack = Optional.empty();
if (ackFilter && !unackFilter || !ackFilter && unackFilter) {
ack = Optional.of(ackFilter);
@ -114,5 +114,9 @@ public class AlarmStatusFilter {
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;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -46,14 +45,4 @@ public class Notification extends BaseData<NotificationId> {
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
public enum NotificationDeliveryMethod {
PUSH("push-notification"),
WEB("web"),
EMAIL("email"),
SMS("SMS"),
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.NotificationTemplate;
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.EnumMap;
@ -96,7 +96,7 @@ public class NotificationProcessingContext {
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
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.putAll(info.getTemplateData());
}
@ -108,9 +108,9 @@ public class NotificationProcessingContext {
((HasSubject) template).setSubject(processTemplate(subject, templateContext));
}
if (deliveryMethod == NotificationDeliveryMethod.PUSH) {
PushDeliveryMethodNotificationTemplate pushNotificationTemplate = (PushDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(pushNotificationTemplate.getAdditionalConfig())
if (deliveryMethod == NotificationDeliveryMethod.WEB) {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig())
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
.map(config -> (ObjectNode) config);
if (buttonConfig.isPresent()) {
@ -129,7 +129,8 @@ public class NotificationProcessingContext {
String result = template;
for (Map<String, String> context : contexts) {
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;

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

@ -22,6 +22,9 @@ public enum NotificationType {
DEVICE_INACTIVITY,
ENTITY_ACTION,
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.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 AlarmCommentNotificationInfo implements NotificationInfo {
public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationInfo {
private String comment;
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 Map.of(
return mapOf(
"comment", comment,
"userName", userName,
"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.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -36,25 +38,33 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
private String alarmType;
private UUID alarmId;
private EntityId alarmOriginator;
private String alarmOriginatorName;
private AlarmSeverity alarmSeverity;
private AlarmStatus alarmStatus;
private CustomerId alarmCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return alarmCustomerId;
}
@Override
public Map<String, String> getTemplateData() {
return Map.of(
// TODO: readable status change
return mapOf(
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorName", alarmOriginatorName,
"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;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
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.UUID;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ -32,6 +37,7 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
private UUID deviceId;
private String deviceName;
private String deviceLabel;
private String deviceType;
private CustomerId deviceCustomerId;
@ -42,11 +48,17 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"deviceId", deviceId.toString(),
"deviceName", deviceName,
"deviceLabel", deviceLabel,
"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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
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.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serializable;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NotificationRequestCacheKey implements Serializable {
public class EntitiesLimitNotificationInfo implements NotificationInfo {
private static final long serialVersionUID = 59871139005482170L;
private EntityId originatorEntityId;
private NotificationRuleId ruleId;
private EntityType entityType;
private int threshold;
@Override
public String toString() {
return ruleId + "_" + originatorEntityId;
public Map<String, String> getTemplateData() {
// 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.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
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
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EntityActionNotificationInfo implements RuleOriginatedNotificationInfo {
private EntityType entityType;
private UUID entityId;
private EntityId entityId;
private String entityName;
private ActionType actionType;
private UUID originatorUserId;
@ -47,8 +48,8 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
@Override
public Map<String, String> getTemplateData() {
return Map.of(
"entityType", entityType.name(),
return mapOf(
"entityType", entityId.getEntityType().name(),
"entityId", entityId.toString(),
"entityName", entityName,
"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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
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.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@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.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
@ -28,4 +29,8 @@ public interface NotificationInfo {
@JsonIgnore
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;
import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ -42,7 +42,7 @@ public class RuleEngineComponentLifecycleEventNotificationInfo implements Notifi
@Override
public Map<String, String> getTemplateData() {
return Map.of(
return mapOf(
"ruleChainId", ruleChainId.toString(),
"ruleChainName", ruleChainName,
"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;
}
@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
private NotificationRuleTriggerType triggerType;
@NotNull
@Valid
private NotificationRuleTriggerConfig triggerConfig;
@NotNull
@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;
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 AlarmCommentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
private Set<AlarmSearchStatus> alarmStatuses;
private boolean onlyUserComments;
@Override
public NotificationRuleTriggerType getTriggerType() {
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;
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.AlarmStatus;
import javax.validation.constraints.NotEmpty;
import java.util.Set;
@Data
@ -26,6 +27,9 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
private Set<String> alarmTypes;
private Set<AlarmSeverity> alarmSeverities;
@NotEmpty
private Set<AlarmAction> notifyOn;
private ClearRule clearRule;
@Override
@ -35,7 +39,11 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
@Data
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 = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"),
@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 {

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;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum NotificationRuleTriggerType {
ALARM(true),
ALARM_COMMENT(true),
DEVICE_INACTIVITY(false),
ENTITY_ACTION(false),
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT(false);
@Getter
private final boolean updatable;
ALARM,
ALARM_COMMENT,
DEVICE_INACTIVITY,
ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION,
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
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));
@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,
ALL_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)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method")
@JsonSubTypes({
@Type(name = "PUSH", value = PushDeliveryMethodNotificationTemplate.class),
@Type(name = "WEB", value = WebDeliveryMethodNotificationTemplate.class),
@Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class),
@Type(name = "SMS", value = SmsDeliveryMethodNotificationTemplate.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
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
private String subject;
private JsonNode additionalConfig;
public PushDeliveryMethodNotificationTemplate(PushDeliveryMethodNotificationTemplate other) {
public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
super(other);
this.subject = other.subject;
this.additionalConfig = other.additionalConfig;
@ -39,12 +39,12 @@ public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotifi
@Override
public NotificationDeliveryMethod getMethod() {
return NotificationDeliveryMethod.PUSH;
return NotificationDeliveryMethod.WEB;
}
@Override
public PushDeliveryMethodNotificationTemplate copy() {
return new PushDeliveryMethodNotificationTemplate(this);
public WebDeliveryMethodNotificationTemplate copy() {
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.NoArgsConstructor;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType;
@AllArgsConstructor
@ -107,6 +108,25 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
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
public TenantProfileType getType() {
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
* 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.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -47,4 +52,19 @@ public class CollectionsUtil {
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.concurrent.ConcurrentHashMap;
import static org.thingsboard.common.util.CollectionsUtil.diffSets;
import static org.thingsboard.server.common.data.util.CollectionsUtil.diffSets;
@Data
@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.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.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID;
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 {
if (sparkplugSessionHandler == null) {
SparkplugTopic sparkplugTopicNode = validatedSparkplugTopicConnectedNode(connectMessage);
@ -1069,6 +1069,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
sparkplugSessionHandler = new SparkplugNodeSessionHandler(this, deviceSessionCtx, sessionId, sparkplugTopicNode);
sparkplugSessionHandler.onAttributesTelemetryProto(0, sparkplugBProtoNode,
deviceSessionCtx.getDeviceInfo().getDeviceName(), sparkplugTopicNode);
sessionMetaData.setOverwriteActivityTime(true);
} else {
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);
@ -1145,7 +1146,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
public void onSuccess(Void msg) {
SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this);
if (deviceSessionCtx.isSparkplug()) {
checkSparkplugNodeSession(connectMessage, ctx);
checkSparkplugNodeSession(connectMessage, ctx, sessionMetaData);
} else {
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.core.annotation.Order;
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.server.common.data.EntityType;
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 {
deleteEntityRelations(tenantId, alarm.getId());
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;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.thingsboard.server.common.data.EntityType;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
private final ApplicationContext applicationContext;
private final Map<EntityType, EntityDaoService> entityDaoServicesMap;
private final Map<EntityType, EntityDaoService> entityDaoServicesMap = new HashMap<>();
public DefaultEntityServiceRegistry(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.entityDaoServicesMap = new HashMap<>();
}
@PostConstruct
@EventListener(ContextRefreshedEvent.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public void init() {
log.debug("Initializing EntityServiceRegistry on ContextRefreshedEvent");
applicationContext.getBeansOfType(EntityDaoService.class).values().forEach(entityDaoService -> {
EntityType entityType = entityDaoService.getEntityType();
entityDaoServicesMap.put(entityType, entityDaoService);
@ -43,6 +47,7 @@ public class DefaultEntityServiceRegistry implements EntityServiceRegistry {
entityDaoServicesMap.put(EntityType.RULE_NODE, entityDaoService);
}
});
log.debug("Initialized EntityServiceRegistry total [{}] entries", entityDaoServicesMap.size());
}
@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_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration";
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_TYPE_PROPERTY = "type";
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_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.page.PageData;
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.notification.cache.NotificationRequestCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRequestCacheValue;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
@ -42,7 +39,7 @@ import java.util.Optional;
@Service
@Slf4j
@RequiredArgsConstructor
public class DefaultNotificationRequestService extends AbstractCachedEntityService<NotificationRequestCacheKey, NotificationRequestCacheValue, NotificationRequest> implements NotificationRequestService, EntityDaoService {
public class DefaultNotificationRequestService implements NotificationRequestService, EntityDaoService {
private final NotificationRequestDao notificationRequestDao;
@ -51,14 +48,7 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
@Override
public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId);
try {
notificationRequest = notificationRequestDao.save(tenantId, notificationRequest);
publishEvictEvent(notificationRequest);
} catch (Exception e) {
handleEvictEvent(notificationRequest);
throw e;
}
return notificationRequest;
return notificationRequestDao.save(tenantId, notificationRequest);
}
@Override
@ -88,21 +78,13 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
@Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) {
NotificationRequestCacheKey cacheKey = NotificationRequestCacheKey.builder()
.originatorEntityId(originatorEntityId)
.ruleId(ruleId)
.build();
return cache.getAndPutInTransaction(cacheKey, () -> NotificationRequestCacheValue.builder()
.notificationRequests(notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId))
.build(), false)
.getNotificationRequests();
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId);
}
// ON DELETE CASCADE is used: notifications for request are deleted as well
@Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
publishEvictEvent(notificationRequest);
notificationRequestDao.removeById(tenantId, notificationRequest.getUuidId());
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId requestId) {
notificationRequestDao.removeById(tenantId, requestId.getId());
}
@Override
@ -120,17 +102,6 @@ public class DefaultNotificationRequestService extends AbstractCachedEntityServi
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
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
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 org.springframework.stereotype.Service;
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.UserId;
import org.thingsboard.server.common.data.notification.Notification;
@ -77,11 +76,6 @@ public class DefaultNotificationService implements NotificationService {
return notificationDao.countUnreadByRecipientId(tenantId, recipientId);
}
@Override
public void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) {
notificationDao.updateStatusesByRequestId(tenantId, requestId, status);
}
@Override
public boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId 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)) {
return userService.findUsersByTenantId(tenantId, pageLink);
} else {
return userService.findUsers(TenantId.SYS_TENANT_ID, pageLink);
return userService.findAllUsers(TenantId.SYS_TENANT_ID, pageLink);
}
}
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);
void updateStatusesByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId);
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 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.EntityType;
import org.thingsboard.server.common.data.StringUtils;
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.exception.DataValidationException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
import java.util.Iterator;
@ -32,6 +34,8 @@ import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.capitalize;
@Slf4j
public abstract class DataValidator<D extends BaseData<?>> {
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 TOPIC = "topic";
@Autowired @Lazy
private ApiLimitService apiLimitService;
// Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try {
@ -97,15 +104,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
}
protected void validateNumberOfEntitiesPerTenant(TenantId tenantId,
TenantEntityDao tenantEntityDao,
long maxEntities,
EntityType entityType) {
if (maxEntities > 0) {
long currentEntitiesCount = tenantEntityDao.countByTenantId(tenantId);
if (currentEntitiesCount >= maxEntities) {
throw new DataValidationException(String.format("Can't create more then %d %ss!",
maxEntities, entityType.name().toLowerCase().replaceAll("_", " ")));
}
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
throw new DataValidationException(String.format("%ss limit reached", capitalize(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.id.CustomerId;
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.BaseAssetService;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -48,17 +46,10 @@ public class AssetDataValidator extends DataValidator<Asset> {
@Autowired
private CustomerDao customerDao;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
long maxAssets = profileConfiguration.getMaxAssets();
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
validateNumberOfEntitiesPerTenant(tenantId, 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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
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.CustomerServiceImpl;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Optional;
@ -41,17 +38,9 @@ public class CustomerDataValidator extends DataValidator<Customer> {
@Autowired
private TenantService tenantService;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
protected void validateCreate(TenantId tenantId, Customer customer) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxCustomers = profileConfiguration.getMaxCustomers();
validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.CUSTOMER);
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
c -> {
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