Browse Source

Notification rules; refactoring

pull/7980/head
ViacheslavKlimov 3 years ago
parent
commit
b7f3aef044
  1. 5
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  3. 6
      application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java
  4. 32
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  5. 173
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  10. 233
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java
  11. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java
  12. 49
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java
  13. 53
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java
  14. 53
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java
  15. 31
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
  16. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  17. 5
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  18. 4
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  19. 35
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  20. 24
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  21. 1
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java
  22. 6
      application/src/main/resources/thingsboard.yml
  23. 13
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  24. 12
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  25. 6
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java
  26. 190
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  27. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java
  28. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java
  29. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  30. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  31. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java
  32. 24
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmOriginatedNotificationInfo.java
  34. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java
  35. 18
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java
  36. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java
  37. 19
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java
  38. 24
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  39. 45
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java
  40. 41
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java
  41. 36
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java
  42. 34
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java
  43. 34
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java
  44. 31
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java
  45. 5
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  46. 3
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  47. 30
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  48. 1
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java
  49. 47
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  50. 6
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java
  51. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java
  52. 5
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  53. 43
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java
  54. 37
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java
  55. 32
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java
  56. 35
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java
  57. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java
  58. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  59. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java
  60. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  61. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  62. 5
      dao/src/main/resources/sql/schema-entities.sql
  63. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java
  64. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  65. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  66. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java
  67. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

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

@ -39,8 +39,9 @@ CREATE TABLE IF NOT EXISTS notification_rule (
tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255) NOT NULL,
configuration VARCHAR(2000) NOT NULL
trigger_type VARCHAR(50) NOT NULL,
trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC);

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

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
@ -50,7 +51,6 @@ import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

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

@ -15,15 +15,19 @@
*/
package org.thingsboard.server.service.executors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.AbstractListeningExecutor;
@Component
public class NotificationExecutorService extends AbstractListeningExecutor {
@Value("${notification_system.thread_pool_size}")
private int notificationSystemExecutorThreadPoolSize;
@Override
protected int getThreadPollSize() {
return 10; // FIXME [viacheslav]
return notificationSystemExecutorThreadPoolSize;
}
}

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

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
@ -92,7 +93,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId());
@ -120,6 +120,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
}
log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
@ -282,11 +283,27 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
}
@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: FIXME: causes each subscription to fetch notifications on each request update
notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT);
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);// TODO: add caching
notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId);
// todo: check delivery method ?
if (notificationRequest.isSent()) {
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId)
@ -296,18 +313,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
}
@Override
public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Updating notification request {}", notificationRequest.getId());
notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequest.getId())
.notificationInfo(notificationRequest.getInfo())
.deleted(false)
.build());
return notificationRequest;
}
private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) {
TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
@ -331,7 +336,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) {
// todo: check delivery method
log.trace("Submitting notification request update: {}", update);
wsCallBackExecutor.submit(() -> {
TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update);

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

@ -1,173 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.rule.NotificationEscalation;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.NotificationExecutorService;
import java.util.List;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@RequiredArgsConstructor
@Slf4j
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService {
private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService;
@Autowired @Lazy
private NotificationCenter notificationCenter;
private final NotificationExecutorService notificationExecutor;
@Override
public ListenableFuture<Void> onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) {
return processAlarmUpdate(tenantId, alarm, false);
}
@Override
public ListenableFuture<Void> onAlarmDeleted(TenantId tenantId, Alarm alarm) {
return processAlarmUpdate(tenantId, alarm, true);
}
private ListenableFuture<Void> processAlarmUpdate(TenantId tenantId, Alarm alarm, boolean deleted) {
NotificationRuleId ruleId = alarm.getNotificationRuleId();
if (ruleId == null) return Futures.immediateFuture(null);
return notificationExecutor.submit(() -> {
try {
onAlarmUpdate(tenantId, ruleId, alarm, deleted);
} catch (Exception e) {
log.error("Failed to process notification rule {} for alarm {}", ruleId, alarm.getId(), e);
throw e;
}
return null;
});
}
private void onAlarmUpdate(TenantId tenantId, NotificationRuleId notificationRuleId, Alarm alarm, boolean deleted) {
log.debug("Processing alarm update ({}) with notification rule {}", alarm.getId(), notificationRuleId);
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, notificationRuleId, alarm.getId());
NotificationRule notificationRule = notificationRuleService.findNotificationRuleById(tenantId, notificationRuleId);
if (notificationRule == null) return;
if (alarmAcknowledged(alarm) || deleted) {
if (notificationRequests.isEmpty()) {
return;
}
for (NotificationRequest notificationRequest : notificationRequests) {
if (notificationRequest.getStatus() == NotificationRequestStatus.SCHEDULED) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequest.getId());
}
}
}
if (notificationRequests.isEmpty()) {
NotificationRuleConfig config = notificationRule.getConfiguration();
for (NotificationEscalation escalation : config.getEscalations()) {
for (NotificationTargetId targetId : escalation.getNotificationTargets()) {
submitNotificationRequest(tenantId, targetId, notificationRule, alarm, escalation.getDelayInSec());
}
}
} else {
NotificationInfo newNotificationInfo = constructNotificationInfo(alarm);
for (NotificationRequest notificationRequest : notificationRequests) {
NotificationInfo previousNotificationInfo = notificationRequest.getInfo();
if (!previousNotificationInfo.equals(newNotificationInfo)) {
notificationRequest.setInfo(newNotificationInfo);
notificationCenter.updateNotificationRequest(tenantId, notificationRequest);
}
}
}
}
private boolean alarmAcknowledged(Alarm alarm) {
return alarm.getStatus().isAck() && alarm.getStatus().isCleared();
}
private void submitNotificationRequest(TenantId tenantId, NotificationTargetId targetId, NotificationRule notificationRule, Alarm alarm, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec);
}
NotificationInfo notificationInfo = constructNotificationInfo(alarm);
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.targets(List.of(targetId).stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationRule.getTemplateId())
.additionalConfig(config)
.info(notificationInfo)
.ruleId(notificationRule.getId())
.originatorEntityId(alarm.getId())
.build();
notificationCenter.processNotificationRequest(tenantId, notificationRequest);
}
private NotificationInfo constructNotificationInfo(Alarm alarm) {
// TODO: add info about assignee
return AlarmOriginatedNotificationInfo.builder()
.alarmId(alarm.getId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.customerId(alarm.getCustomerId())
.build();
}
@EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
componentLifecycleMsg.getEntityId().getEntityType() != EntityType.NOTIFICATION_RULE) {
return;
}
TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
}
}
}

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

@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component
@RequiredArgsConstructor

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

@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
public interface NotificationChannel<T extends DeliveryMethodNotificationTemplate> {

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

@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component
@RequiredArgsConstructor

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

@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.service.sms.SmsExecutorService;
@Component

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

@ -0,0 +1,233 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.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.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleEngineOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
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.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.NotificationRuleTriggerProcessor;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
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
);
@Override
public void process(TenantId tenantId, TbMsg ruleEngineMsg) {
String msgType = ruleEngineMsg.getType();
NotificationRuleTriggerType triggerType = msgTypeToTriggerType.get(msgType);
if (triggerType == null) {
return;
}
processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg, false, () -> {
return RuleEngineOriginatedNotificationInfo.builder()
.msgOriginator(ruleEngineMsg.getOriginator())
.msgType(ruleEngineMsg.getType())
.msgMetadata(ruleEngineMsg.getMetaData().getData())
.build();
});
}
@Override
public void process(TenantId tenantId, Alarm alarm, boolean deleted) {
processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), alarm, deleted, () -> {
// TODO: add info about assignee
return AlarmOriginatedNotificationInfo.builder()
.alarmId(alarm.getId())
.alarmType(alarm.getType())
.alarmOriginator(alarm.getOriginator())
.alarmSeverity(alarm.getSeverity())
.alarmStatus(alarm.getStatus())
.customerId(alarm.getCustomerId())
.build();
});
}
private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId,
Object triggerObject, boolean triggerRemoved,
Supplier<NotificationInfo> notificationInfoProvider) {
ListenableFuture<List<NotificationRule>> rulesFuture = dbCallbackExecutor.submit(() -> {
return notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
});
DonAsynchron.withCallback(rulesFuture, rules -> {
for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> {
processNotificationRule(rule, originatorEntityId, triggerObject, triggerRemoved, notificationInfoProvider);
});
}
}, e -> {});
}
private <T> void processNotificationRule(NotificationRule rule, EntityId originatorEntityId,
T triggerObject, boolean triggerRemoved,
Supplier<NotificationInfo> notificationInfoProvider) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (triggerConfig.getTriggerType().isUpdatable()) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), originatorEntityId);
if (!notificationRequests.isEmpty()) {
if (triggerRemoved || 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 = notificationInfoProvider.get();
for (NotificationRequest notificationRequest : notificationRequests) {
NotificationInfo previousNotificationInfo = notificationRequest.getInfo();
if (!notificationInfo.equals(previousNotificationInfo)) {
notificationRequest.setInfo(notificationInfo);
// and make notifications unread ?
dbCallbackExecutor.submit(() -> {
notificationCenter.updateNotificationRequest(rule.getTenantId(), notificationRequest);
});
}
}
return;
}
}
if (!matchesFilter(triggerObject, triggerConfig)) {
return;
}
NotificationInfo notificationInfo = notificationInfoProvider.get();
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
notificationExecutor.submit(() -> {
try {
log.debug("Submitting notification request for rule '{}' with delay of {} ms to targets {}", rule.getName(), delay, targets);
submitNotificationRequest(targets, rule, originatorEntityId, notificationInfo, delay);
} catch (Exception e) {
log.error("Failed to submit notification request for rule {}", rule.getId(), e);
}
});
});
}
private boolean matchesFilter(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(triggerObject, triggerConfig);
}
private boolean matchesClearRule(Object triggerObject, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(triggerObject, triggerConfig);
}
private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec);
}
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(rule.getTenantId())
.targets(targets)
.templateId(rule.getTemplateId())
.additionalConfig(config)
.info(notificationInfo)
.ruleId(rule.getId())
.originatorEntityId(originatorEntityId)
.build();
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest);
}
@EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
componentLifecycleMsg.getEntityId().getEntityType() != EntityType.NOTIFICATION_RULE) {
return;
}
TenantId tenantId = componentLifecycleMsg.getTenantId();
NotificationRuleId notificationRuleId = (NotificationRuleId) componentLifecycleMsg.getEntityId();
dbCallbackExecutor.submit(() -> {
List<NotificationRequestId> scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId);
for (NotificationRequestId notificationRequestId : scheduledForRule) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId);
}
});
}
@Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
this.triggerProcessors = processors.stream()
.collect(Collectors.toMap(NotificationRuleTriggerProcessor::getTriggerType, p -> p));
}
}

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

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
package org.thingsboard.server.service.notification.rule;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
public interface NotificationRuleProcessingService {
ListenableFuture<Void> onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm);
void process(TenantId tenantId, TbMsg ruleEngineMsg);
ListenableFuture<Void> onAlarmDeleted(TenantId tenantId, Alarm alarm);
void process(TenantId tenantId, Alarm alarm, boolean deleted);
}

49
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
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.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Service
public class AlarmNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor<Alarm, AlarmNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return (CollectionUtils.isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
(CollectionUtils.isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity()));
}
@Override
public boolean matchesClearRule(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
AlarmNotificationRuleTriggerConfig.ClearRule clearRule = triggerConfig.getClearRule();
if (clearRule != null) {
if (clearRule.getAlarmStatus() != null) {
return clearRule.getAlarmStatus().equals(alarm.getStatus());
}
}
return false;
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ALARM;
}
}

53
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceInactivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@Service
@RequiredArgsConstructor
public class DeviceInactivityNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, DeviceInactivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache;
@Override
public boolean matchesFilter(TbMsg ruleEngineMsg, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) {
DeviceId deviceId = (DeviceId) ruleEngineMsg.getOriginator();
if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) {
return triggerConfig.getDevices().contains(deviceId);
} else if (CollectionUtils.isNotEmpty(triggerConfig.getDeviceProfiles())) {
DeviceProfile deviceProfile = deviceProfileCache.get(TenantId.SYS_TENANT_ID, deviceId);
return deviceProfile != null && triggerConfig.getDeviceProfiles().contains(deviceProfile.getId());
} else {
return true;
}
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.DEVICE_INACTIVITY;
}
}

53
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
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.msg.TbMsg;
@Service
public class EntityActionNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor<TbMsg, EntityActionNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(TbMsg ruleEngineMsg, EntityActionNotificationRuleTriggerConfig triggerConfig) {
String msgType = ruleEngineMsg.getType();
if (msgType.equals(DataConstants.ENTITY_CREATED)) {
if (!triggerConfig.isCreated()) {
return false;
}
} else if (msgType.equals(DataConstants.ENTITY_UPDATED)) {
if (!triggerConfig.isUpdated()) {
return false;
}
} else if (msgType.equals(DataConstants.ENTITY_DELETED)){
if (!triggerConfig.isDeleted()) {
return false;
}
} else {
return false;
}
return triggerConfig.getEntityType() == null || ruleEngineMsg.getOriginator().getEntityType() == triggerConfig.getEntityType();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITY_ACTION;
}
}

31
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java

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

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

@ -69,6 +69,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.notification.NotificationSchedulerService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -155,9 +156,10 @@ 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, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

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

@ -52,6 +52,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
@ -128,8 +129,9 @@ 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, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory;
@ -480,6 +482,7 @@ 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,6 +34,7 @@ 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;
@ -77,6 +78,7 @@ 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;
@ -86,6 +88,7 @@ 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;
@ -95,6 +98,7 @@ 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;
}

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

@ -17,8 +17,6 @@ package org.thingsboard.server.service.subscription;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
@ -44,7 +42,6 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.service.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmSubscriptionUpdateProto;
@ -61,6 +58,7 @@ 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;
@ -297,7 +295,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm,
false
);
notificationRuleProcessingService.onAlarmCreatedOrUpdated(tenantId, alarm);
notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess();
}
@ -315,7 +313,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm,
true
);
notificationRuleProcessingService.onAlarmDeleted(tenantId, alarm);
notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess();
}
@ -345,15 +343,22 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
@Override
public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate);
subscriptionsByEntityId.entrySet().stream()
.filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER)
.flatMap(subEntry -> subEntry.getValue().stream()
.filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS
|| sub.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.filter(sub -> sub.getServiceId().equals(serviceId)))
.forEach(subscription -> {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
});
subscriptionsByEntityId.forEach((entityId, subscriptions) -> {
if (entityId.getEntityType() != EntityType.USER) {
return;
}
subscriptions.forEach(subscription -> {
if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS &&
subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) {
return;
}
if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) {
return;
}
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(),
subscriptionUpdate, TbCallback.EMPTY);
});
});
callback.onSuccess();
}
@ -386,7 +391,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId,
new DeviceId(entityId.getId()), scope, keys), null);
new DeviceId(entityId.getId()), scope, keys), null);
}
}
callback.onSuccess();

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

@ -23,7 +23,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
@ -175,21 +174,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
NotificationRequestId notificationRequestId = update.getNotificationRequestId();
if (update.isDeleted()) {
if (subscription.getLatestUnreadNotifications().values().stream()
.anyMatch(notification -> notificationRequestId.equals(notification.getRequestId()))) {
fetchUnreadNotifications(subscription);
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
}
} else {
subscription.getLatestUnreadNotifications().values().stream()
.filter(notification -> notificationRequestId.equals(notification.getRequestId()))
.forEach(notification -> {
notification.setInfo(update.getNotificationInfo());
sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification));
});
}
fetchUnreadNotifications(subscription); // FIXME: figure out how not to fetch notifications on each request update...
sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
}
@ -230,10 +216,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
if (update.isDeleted()) {
fetchUnreadNotificationsCount(subscription);
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
fetchUnreadNotificationsCount(subscription); // FIXME: figure out how not to fetch notifications on each request update...
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}

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

@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@Builder
public class NotificationRequestUpdate {
private NotificationRequestId notificationRequestId;
private String notificationReason;
private NotificationInfo notificationInfo;
private boolean deleted;
}

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

@ -433,6 +433,9 @@ cache:
assetProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}"
notificationRules:
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}"
@ -1204,6 +1207,9 @@ vc:
io_pool_size: "${TB_VC_GIT_POOL_SIZE:3}"
repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}"
notification_system:
thread_pool_size: "${TB_NOTIFICATION_SYSTEM_THREAD_POOL_SIZE:30}"
management:
endpoints:
web:

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

@ -197,16 +197,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
return wsClient;
}
protected void connectWsClient() throws Exception {
loginCustomerUser();
wsClient = (NotificationApiWsClient) super.getWsClient();
loginTenantAdmin();
}
protected void connectOtherWsClient() throws Exception {
loginCustomerUser();
otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient();
loginTenantAdmin();
@Override
public NotificationApiWsClient getWsClient() {
return (NotificationApiWsClient) super.getWsClient();
}
}

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

@ -90,7 +90,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Before
public void beforeEach() throws Exception {
connectWsClient();
loginCustomerUser();
wsClient = getWsClient();
loginTenantAdmin();
}
@Test
@ -274,7 +276,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationRequest.setInfo(newNotificationInfo);
notificationCenter.updateNotificationRequest(tenantId, notificationRequest);
wsClient.waitForUpdate(true);
Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate();
Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().iterator().next();
assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo);
assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification);
}
@ -583,4 +585,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(notificationsUpdate.getTotalUnreadCount()).isEqualTo(expectedUnreadCount);
}
protected void connectOtherWsClient() throws Exception {
loginCustomerUser();
otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient();
loginTenantAdmin();
}
}

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

@ -52,17 +52,19 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token));
}
public void subscribeForUnreadNotifications(int limit) {
public NotificationApiWsClient subscribeForUnreadNotifications(int limit) {
NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit));
sendCmd(cmdsWrapper);
this.limit = limit;
return this;
}
public void subscribeForUnreadNotificationsCount() {
public NotificationApiWsClient subscribeForUnreadNotificationsCount() {
NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2));
sendCmd(cmdsWrapper);
return this;
}
public void markNotificationAsRead(UUID... notifications) {

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

@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
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.AlarmSeverity;
@ -37,13 +38,16 @@ import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.rule.NotificationEscalation;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
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.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -58,14 +62,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;
import static org.assertj.core.api.Assertions.offset;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@ -79,40 +85,98 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
}
@Test
public void testNotificationRuleProcessing() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL};
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, "New alarm", "NEW ALARM ${alarmType}", deliveryMethods);
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${msgType}: ${originatorType} [${originatorId}]";
String notificationText = "User: ${userName}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH);
NotificationRule notificationRule = new NotificationRule();
notificationRule.setTenantId(tenantId);
notificationRule.setName("Test rule for my alarms");
notificationRule.setName("Push-notification when any device is created, updated or deleted");
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setDeliveryMethods(List.of(deliveryMethods));
NotificationRuleConfig config = new NotificationRuleConfig();
notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig();
triggerConfig.setEntityType(EntityType.DEVICE);
triggerConfig.setCreated(true);
triggerConfig.setUpdated(true);
triggerConfig.setDeleted(true);
DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
recipientsConfig.setTargets(List.of(createNotificationTarget(tenantAdminUserId).getUuidId()));
notificationRule.setTriggerConfig(triggerConfig);
notificationRule.setRecipientsConfig(recipientsConfig);
notificationRule = saveNotificationRule(notificationRule);
getWsClient().subscribeForUnreadNotifications(10).waitForReply(true);
List<NotificationEscalation> escalations = new ArrayList<>();
getWsClient().registerWaitForUpdate();
Device device = createDevice("DEVICE!!!", "default", "12345");
getWsClient().waitForUpdate(true);
Notification notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("ENTITY_CREATED: DEVICE [" + device.getId() + "]");
assertThat(notification.getText()).isEqualTo("User: " + TENANT_ADMIN_EMAIL);
getWsClient().registerWaitForUpdate();
device.setName("Updated name");
device = doPost("/api/device", device, Device.class);
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("ENTITY_UPDATED: DEVICE [" + device.getId() + "]");
getWsClient().registerWaitForUpdate();
doDelete("/api/device/" + device.getId()).andExpect(status().isOk());
getWsClient().waitForUpdate(true);
notification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(notification.getSubject()).isEqualTo("ENTITY_DELETED: DEVICE [" + device.getId() + "]");
System.err.println(notification);
}
@Test
public void testNotificationRuleProcessing_alarmTrigger() throws Exception {
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);
NotificationRule notificationRule = new NotificationRule();
notificationRule.setName("Push-notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null);
notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM);
Map<Integer, List<UUID>> escalationTable = new HashMap<>();
recipientsConfig.setEscalationTable(escalationTable);
Map<Integer, NotificationApiWsClient> clients = new HashMap<>();
for (int delay = 0; delay <= 5; delay++) {
Pair<User, NotificationApiWsClient> userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN);
NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId());
NotificationEscalation escalation = new NotificationEscalation();
escalation.setDelayInSec(delay);
escalation.setNotificationTargets(List.of(notificationTarget.getId()));
escalations.add(escalation);
escalationTable.put(delay, List.of(notificationTarget.getUuidId()));
clients.put(delay, userAndClient.getSecond());
}
config.setEscalations(escalations);
notificationRule.setConfiguration(config);
notificationRule.setRecipientsConfig(recipientsConfig);
notificationRule = saveNotificationRule(notificationRule);
String alarmType = "boolIsTrue";
String alarmType = "myBoolIsTrue";
DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType);
Device device = createDevice("Device 1", deviceProfile.getName(), "1234");
clients.values().forEach(wsClient -> {
wsClient.subscribeForUnreadNotifications(10);
wsClient.waitForReply();
wsClient.subscribeForUnreadNotifications(10).waitForReply(true);
wsClient.registerWaitForUpdate();
});
@ -122,9 +186,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType)));
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get();
assertThat(alarm.getNotificationRuleId()).isEqualTo(notificationRule.getId());
long ts = System.currentTimeMillis();
long ts = System.currentTimeMillis();
await().atMost(7, TimeUnit.SECONDS)
.until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null));
clients.forEach((expectedDelay, wsClient) -> {
@ -132,27 +195,88 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
double actualDelay = (double) (notification.getCreatedTime() - ts) / 1000;
assertThat(actualDelay).isCloseTo(expectedDelay, offset(0.5));
assertThat(notification.getText()).isEqualTo("NEW ALARM " + alarm.getType());
AlarmStatus expectedStatus = AlarmStatus.ACTIVE_UNACK;
AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL;
assertThat(notification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " +
"severity: " + expectedSeverity + ", deviceId: " + device.getId());
assertThat(notification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity);
assertThat(notification.getType()).isEqualTo(NotificationType.ALARM);
assertThat(notification.getSubject()).isEqualTo("New alarm");
assertThat(notification.getInfo()).isInstanceOf(AlarmOriginatedNotificationInfo.class);
AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo();
assertThat(info.getAlarmId()).isEqualTo(alarm.getId());
assertThat(info.getAlarmType()).isEqualTo(alarm.getType());
assertThat(info.getAlarmSeverity()).isEqualTo(AlarmSeverity.CRITICAL);
assertThat(info.getAlarmStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK);
assertThat(info.getAlarmType()).isEqualTo(alarmType);
assertThat(info.getAlarmSeverity()).isEqualTo(expectedSeverity);
assertThat(info.getAlarmStatus()).isEqualTo(expectedStatus);
});
clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate());
alarmSubscriptionService.ackAlarm(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().getUpdate();
assertThat(((AlarmOriginatedNotificationInfo) updatedNotification.getInfo()).getAlarmStatus())
.isEqualTo(AlarmStatus.ACTIVE_ACK);
Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().stream().findFirst().get();
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 clear rule + alarm not escalated
// TODO: test severity changes
}
@Test
public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception {
/*
String notificationSubject = "New alarm '${alarmType}'";
String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH);
NotificationRule notificationRule = new NotificationRule();
notificationRule.setName("Push-notification on any alarm");
notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM);
AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig();
triggerConfig.setAlarmTypes(null);
triggerConfig.setAlarmSeverities(null);
notificationRule.setTriggerConfig(triggerConfig);
EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM);
Map<Integer, List<UUID>> escalationTable = new HashMap<>();
recipientsConfig.setEscalationTable(escalationTable);
Map<Integer, NotificationApiWsClient> clients = new HashMap<>();
for (int delay = 0; delay <= 5; delay++) {
Pair<User, NotificationApiWsClient> userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN);
NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId());
escalationTable.put(delay, List.of(notificationTarget.getUuidId()));
clients.put(delay, userAndClient.getSecond());
}
notificationRule.setRecipientsConfig(recipientsConfig);
notificationRule = saveNotificationRule(notificationRule);
String alarmType = "myBoolIsTrue";
DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType);
Device device = createDevice("Device 1", deviceProfile.getName(), "1234");
clients.values().forEach(wsClient -> {
wsClient.subscribeForUnreadNotifications(10).waitForReply(true);
wsClient.registerWaitForUpdate();
});
JsonNode attr = JacksonUtil.newObjectNode()
.set("bool", BooleanNode.TRUE);
doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr);
verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType)));
Alarm alarm = alarmSubscri
*/
}
private DeviceProfile createDeviceProfileWithAlarmRules(NotificationRuleId notificationRuleId, String alarmType) {

5
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java

@ -18,9 +18,12 @@ package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface NotificationRuleService {
NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule);
@ -29,6 +32,8 @@ public interface NotificationRuleService {
PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink);
List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);
void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id);
}

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

@ -16,9 +16,11 @@
package org.thingsboard.server.dao.notification;
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;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -36,6 +38,8 @@ public interface NotificationService {
int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId);
void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId);
}

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

@ -29,6 +29,7 @@ public class CacheConstants {
public static final String TENANTS_CACHE = "tenants";
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 ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -81,7 +81,6 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes;
private NotificationRuleId notificationRuleId;
public Alarm() {
super();
@ -109,7 +108,6 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
this.propagateRelationTypes = alarm.getPropagateRelationTypes();
this.notificationRuleId = alarm.getNotificationRuleId();
}
@Override

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.notification;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -43,4 +44,14 @@ 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);
}
}

24
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java

@ -13,20 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
package org.thingsboard.server.common.data.notification;
import com.google.common.base.Strings;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
@ -91,7 +88,7 @@ public class NotificationProcessingContext {
}
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
if (request.getInfo() != null) {
if (request.getInfo() != null && deliveryMethod != NotificationDeliveryMethod.PUSH) { // for push notifications we are processing template from info on each serialization
templateContext = new HashMap<>(templateContext);
templateContext.putAll(request.getInfo().getTemplateData());
}
@ -105,8 +102,19 @@ public class NotificationProcessingContext {
return template;
}
private <T extends DeliveryMethodNotificationTemplate> String processTemplate(String template, Map<String, String> context) {
return TbNodeUtils.processTemplate(template, context);
private static String processTemplate(String template, Map<String, String> context) {
if (template == null || context.isEmpty()) return template;
String result = template;
for (Map.Entry<String, String> kv : context.entrySet()) {
result = result.replace("${" + kv.getKey() + '}', kv.getValue());
}
return result;
}
public static String processTemplate(String template, NotificationInfo notificationInfo) {
if (notificationInfo == null) return template;
Map<String, String> templateContext = notificationInfo.getTemplateData();
return processTemplate(template, templateContext);
}
public Map<String, String> createTemplateContext(User recipient) {

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

@ -51,6 +51,8 @@ public class AlarmOriginatedNotificationInfo implements NotificationInfo {
return Map.of(
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().toString()
);

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

@ -29,7 +29,7 @@ import java.util.Map;
@JsonSubTypes({
@Type(name = "USER", value = UserOriginatedNotificationInfo.class),
@Type(name = "ALARM", value = AlarmOriginatedNotificationInfo.class),
@Type(name = "RULE_NODE", value = RuleNodeOriginatedNotificationInfo.class)
@Type(name = "RULE_CHAIN", value = RuleEngineOriginatedNotificationInfo.class)
})
public interface NotificationInfo {

18
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleNodeOriginatedNotificationInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java

@ -15,26 +15,38 @@
*/
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.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.HashMap;
import java.util.Map;
@Data
public class RuleNodeOriginatedNotificationInfo implements NotificationInfo {
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RuleEngineOriginatedNotificationInfo implements NotificationInfo {
private EntityId msgOriginator;
private String msgType;
private Map<String, String> msgMetadata;
@Override
public EntityType getOriginatorType() {
return EntityType.RULE_NODE;
return EntityType.RULE_CHAIN;
}
@Override
public Map<String, String> getTemplateData() {
return msgMetadata;
Map<String, String> templateData = new HashMap<>(msgMetadata);
templateData.put("originatorType", msgOriginator.getEntityType().toString());
templateData.put("originatorId", msgOriginator.getId().toString());
templateData.put("msgType", msgType);
return templateData;
}
}

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java

@ -16,17 +16,23 @@
package org.thingsboard.server.common.data.notification.rule;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Data
public class NotificationRuleConfig {
@EqualsAndHashCode(callSuper = true)
public class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig {
@NotEmpty
@Valid
private List<NotificationEscalation> escalations;
private List<UUID> targets;
@Override
public Map<Integer, List<UUID>> getTargetsTable() {
return Map.of(0, targets);
}
}

19
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalation.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java

@ -16,20 +16,23 @@
package org.thingsboard.server.common.data.notification.rule;
import lombok.Data;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Data
public class NotificationEscalation {
@EqualsAndHashCode(callSuper = true)
public class EscalatedNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig {
@Max(NotificationRequestConfig.MAX_SENDING_DELAY)
private int delayInSec;
@NotEmpty
private List<NotificationTargetId> notificationTargets;
private Map<Integer, List<UUID>> escalationTable;
@Override
public Map<Integer, List<UUID>> getTargetsTable() {
return escalationTable;
}
}

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

@ -15,22 +15,22 @@
*/
package org.thingsboard.server.common.data.notification.rule;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ -41,10 +41,20 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private String name;
@NotNull
private NotificationTemplateId templateId;
@NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods;
@NotNull
private NotificationRuleTriggerType triggerType;
@NotNull
private NotificationRuleTriggerConfig triggerConfig;
@NotNull
@Valid
private NotificationRuleConfig configuration; // todo: add pg_tgrm index (but index is 2.5x size of the column)
private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column)
@JsonIgnore
@AssertTrue(message = "trigger type not matching")
public boolean isValid() {
return triggerType == triggerConfig.getTriggerType() &&
triggerType == recipientsConfig.getTriggerType();
}
}

45
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@JsonIgnoreProperties
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = DefaultNotificationRuleRecipientsConfig.class)
@JsonSubTypes({
@Type(name = "ALARM", value = EscalatedNotificationRuleRecipientsConfig.class),
})
@Data
public abstract class NotificationRuleRecipientsConfig {
@NotNull
private NotificationRuleTriggerType triggerType;
@JsonIgnore
public abstract Map<Integer, List<UUID>> getTargetsTable();
}

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

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

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

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

34
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java

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

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

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType")
@JsonSubTypes({
@Type(value = AlarmNotificationRuleTriggerConfig.class, name = "ALARM"),
@Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"),
@Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION")
})
public interface NotificationRuleTriggerConfig {
NotificationRuleTriggerType getTriggerType();
}

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

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum NotificationRuleTriggerType {
ALARM(true),
DEVICE_INACTIVITY(false),
ENTITY_ACTION(false);
@Getter
private final boolean isUpdatable;
}

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

@ -674,8 +674,9 @@ public class ModelConstants {
public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule";
public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id";
public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods";
public static final String NOTIFICATION_RULE_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY = "trigger_type";
public static final String NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY = "trigger_config";
public static final String NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY = "recipients_config";
public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template";
public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type";

3
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -155,7 +155,6 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
} else {
this.propagateRelationTypes = null;
}
this.notificationRuleId = getUuid(alarm.getNotificationRuleId());
}
public AbstractAlarmEntity(AlarmEntity alarmEntity) {
@ -178,7 +177,6 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.clearTs = alarmEntity.getClearTs();
this.details = alarmEntity.getDetails();
this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes();
this.notificationRuleId = alarmEntity.getNotificationRuleId();
}
protected Alarm toAlarm() {
@ -207,7 +205,6 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
} else {
alarm.setPropagateRelationTypes(Collections.emptyList());
}
alarm.setNotificationRuleId(getEntityId(notificationRuleId, NotificationRuleId::new));
return alarm;
}
}

30
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java

@ -22,15 +22,18 @@ import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import java.util.UUID;
@ -49,12 +52,17 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false)
private UUID templateId;
@Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false)
private String deliveryMethods;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY, nullable = false)
private NotificationRuleTriggerType triggerType;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration;
@Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY, nullable = false)
private JsonNode triggerConfig;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY, nullable = false)
private JsonNode recipientsConfig;
public NotificationRuleEntity() {}
@ -64,8 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTenantId(getTenantUuid(notificationRule.getTenantId()));
setName(notificationRule.getName());
setTemplateId(getUuid(notificationRule.getTemplateId()));
setDeliveryMethods(listToString(notificationRule.getDeliveryMethods()));
setConfiguration(toJson(notificationRule.getConfiguration()));
setTriggerType(notificationRule.getTriggerType());
setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
}
@Override
@ -76,8 +85,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTenantId(getTenantId(tenantId));
notificationRule.setName(name);
notificationRule.setTemplateId(getEntityId(templateId, NotificationTemplateId::new));
notificationRule.setDeliveryMethods(listFromString(deliveryMethods, NotificationDeliveryMethod::valueOf));
notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class));
notificationRule.setTriggerType(triggerType);
notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
return notificationRule;
}

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

@ -64,6 +64,7 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
@Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) {
// FIXME: add caching
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId);
}

47
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -20,18 +20,38 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
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.notification.cache.NotificationRuleCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DefaultNotificationRuleService implements NotificationRuleService {
public class DefaultNotificationRuleService extends AbstractCachedEntityService<NotificationRuleCacheKey, NotificationRuleCacheValue, NotificationRule> implements NotificationRuleService {
private final NotificationRuleDao notificationRuleDao;
@Override
public NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule) {
return notificationRuleDao.save(tenantId, notificationRule);
boolean created = notificationRule.getId() == null;
if (!created) {
NotificationRule oldNotificationRule = findNotificationRuleById(tenantId, notificationRule.getId());
if (notificationRule.getTriggerType() != oldNotificationRule.getTriggerType()) {
throw new IllegalArgumentException("Notification rule trigger type cannot be updated");
}
}
try {
notificationRule = notificationRuleDao.saveAndFlush(tenantId, notificationRule);
publishEvictEvent(notificationRule);
} catch (Exception e) {
handleEvictEvent(notificationRule);
throw e;
}
return notificationRule;
}
@Override
@ -44,9 +64,32 @@ public class DefaultNotificationRuleService implements NotificationRuleService {
return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink);
}
@Override
public List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType) {
NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder()
.tenantId(tenantId)
.triggerType(triggerType)
.build();
return cache.getAndPutInTransaction(cacheKey, () -> NotificationRuleCacheValue.builder()
.notificationRules(notificationRuleDao.findByTenantIdAndTriggerType(tenantId, triggerType))
.build(), false)
.getNotificationRules();
}
@Override
public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) {
NotificationRule notificationRule = findNotificationRuleById(tenantId, id);
publishEvictEvent(notificationRule);
notificationRuleDao.removeById(tenantId, id.getId());
}
@Override
public void handleEvictEvent(NotificationRule notificationRule) {
NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder()
.tenantId(notificationRule.getTenantId())
.triggerType(notificationRule.getTriggerType())
.build();
cache.evict(cacheKey);
}
}

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

@ -19,6 +19,7 @@ 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;
@ -71,6 +72,11 @@ public class DefaultNotificationService implements NotificationService {
return notificationDao.countUnreadByUserId(tenantId, userId);
}
@Override
public void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) {
notificationDao.updateStatusesByRequestId(tenantId, requestId, status);
}
@Override
public boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId) {
return notificationDao.deleteByIdAndUserId(tenantId, userId, notificationId);

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

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

5
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java

@ -18,14 +18,19 @@ package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface NotificationRuleDao extends Dao<NotificationRule> {
PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId);
List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);
}

43
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRuleCacheKey implements Serializable {
private static final long serialVersionUID = 5987113265482170L;
private TenantId tenantId;
private NotificationRuleTriggerType triggerType;
@Override
public String toString() {
return tenantId + "_" + triggerType;
}
}

37
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRuleCacheValue implements Serializable {
private static final long serialVersionUID = 9503216785105415L;
private List<NotificationRule> notificationRules;
}

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

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.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("notificationRuleCache")
public class NotificationRuleCaffeineCache extends CaffeineTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.NOTIFICATION_RULES_CACHE);
}
}

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

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.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("notificationRuleCache")
public class NotificationRuleRedisCache extends RedisTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) {
super(CacheConstants.NOTIFICATION_RULES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java

@ -88,6 +88,11 @@ public class JpaNotificationDao extends JpaAbstractDao<NotificationEntity, Notif
return DaoUtil.toPageData(notificationRepository.findByRequestId(notificationRequestId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
public void updateStatusesByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) {
notificationRepository.updateStatusesByRequestId(requestId.getId(), status);
}
@Override
public boolean deleteByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId) {
return notificationRepository.deleteByIdAndRecipientId(notificationId.getId(), userId.getId()) != 0;

9
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil;
@ -31,6 +32,7 @@ import org.thingsboard.server.dao.notification.NotificationRuleDao;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.getId;
@ -50,7 +52,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
@Override
public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) {
return notificationRuleRepository.existsByConfigurationContaining(targetId.getId().toString());
return notificationRuleRepository.existsByRecipientsConfigContaining(targetId.getId().toString());
}
@Override
public List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType) {
return DaoUtil.convertDataList(notificationRuleRepository.findAllByTenantIdAndTriggerType(getId(tenantId, true), triggerType));
}
@Override

7
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java

@ -48,6 +48,13 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
Page<NotificationEntity> findByRequestId(UUID requestId, Pageable pageable);
@Modifying
@Transactional
@Query("UPDATE NotificationEntity n SET n.status = :status " +
"WHERE n.requestId = :requestId AND n.status <> :status")
int updateStatusesByRequestId(@Param("requestId") UUID requestId,
@Param("status") NotificationStatus status);
int deleteByIdAndRecipientId(UUID id, UUID recipientId);
}

6
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java

@ -19,8 +19,10 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import java.util.List;
import java.util.UUID;
@Repository
@ -28,6 +30,8 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
Page<NotificationRuleEntity> findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable);
boolean existsByConfigurationContaining(String string);
boolean existsByRecipientsConfigContaining(String string);
List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType);
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
@ -102,9 +101,6 @@ public class AlarmDataAdapter {
} else {
alarm.setPropagateRelationTypes(Collections.emptyList());
}
if (row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID) != null) {
alarm.setNotificationRuleId(new NotificationRuleId((UUID) row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID)));
}
UUID entityUuid = (UUID) row.get(ModelConstants.ENTITY_ID_COLUMN);
EntityId entityId = entityIdMap.get(entityUuid);
Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY);

5
dao/src/main/resources/sql/schema-entities.sql

@ -803,8 +803,9 @@ CREATE TABLE IF NOT EXISTS notification_rule (
tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255) NOT NULL,
configuration VARCHAR(2000) NOT NULL
trigger_type VARCHAR(50) NOT NULL,
trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL
);
CREATE TABLE IF NOT EXISTS notification_request (

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java

@ -25,10 +25,10 @@ public interface NotificationCenter {
NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text);

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@ -184,7 +183,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
existingAlarm.setDetails(details);
}
existingAlarm.setEndTs(System.currentTimeMillis());
existingAlarm.setNotificationRuleId(config.getNotificationRuleId());
return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
}, ctx.getDbCallbackExecutor());
@ -206,7 +204,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
.startTs(ts)
.endTs(ts)
.details(details)
.notificationRuleId(config.getNotificationRuleId())
.build();
}

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java

@ -18,7 +18,6 @@ package org.thingsboard.rule.engine.action;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.data.validation.NoXss;
@ -39,8 +38,6 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
private List<String> relationTypes;
private NotificationRuleId notificationRuleId;
@Override
public TbCreateAlarmNodeConfiguration defaultConfiguration() {
TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration();

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java

@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.info.RuleNodeOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleEngineOriginatedNotificationInfo;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -50,7 +50,7 @@ public class TbNotificationNode implements TbNode {
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
RuleNodeOriginatedNotificationInfo notificationInfo = new RuleNodeOriginatedNotificationInfo();
RuleEngineOriginatedNotificationInfo notificationInfo = new RuleEngineOriginatedNotificationInfo();
notificationInfo.setMsgOriginator(msg.getOriginator());
notificationInfo.setMsgMetadata(msg.getMetaData().getData());
@ -60,7 +60,7 @@ public class TbNotificationNode implements TbNode {
.templateId(config.getTemplateId())
.info(notificationInfo)
.additionalConfig(new NotificationRequestConfig())
.originatorEntityId(ctx.getSelfId())
.originatorEntityId(ctx.getSelf().getRuleChainId())
.build();
DonAsynchron.withCallback(ctx.getNotificationExecutor().executeAsync(() -> {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -241,7 +241,6 @@ class AlarmState {
// Skip update if severity is decreased.
if (severity.ordinal() <= oldSeverity.ordinal()) {
currentAlarm.setDetails(createDetails(ruleState));
currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId());
if (!oldSeverity.equals(severity)) {
currentAlarm.setSeverity(severity);
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
@ -273,7 +272,6 @@ class AlarmState {
if (alarmDefinition.getPropagateRelationTypes() != null) {
currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes());
}
currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId());
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs();
return new TbAlarmResult(!updated, updated, false, false, currentAlarm);

Loading…
Cancel
Save