Browse Source

Notification rules; refactoring

pull/7980/head
ViacheslavKlimov 4 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, tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255) NOT NULL, trigger_type VARCHAR(50) NOT NULL,
configuration VARCHAR(2000) 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); 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.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview; import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; 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.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.queue.util.TbCoreComponent; 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.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; 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; package org.thingsboard.server.service.executors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.AbstractListeningExecutor;
@Component @Component
public class NotificationExecutorService extends AbstractListeningExecutor { public class NotificationExecutorService extends AbstractListeningExecutor {
@Value("${notification_system.thread_pool_size}")
private int notificationSystemExecutorThreadPoolSize;
@Override @Override
protected int getThreadPollSize() { 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.AlreadySentException;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
@ -92,7 +93,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setTenantId(tenantId); notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId()); 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.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); 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 @Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) {
log.debug("Deleting notification request {}", notificationRequestId); log.debug("Deleting notification request {}", notificationRequestId);
NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);// TODO: add caching NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);// TODO: add caching
notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId); notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId);
// todo: check delivery method ?
if (notificationRequest.isSent()) { if (notificationRequest.isSent()) {
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId) .notificationRequestId(notificationRequestId)
@ -296,18 +313,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); 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) { private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) {
TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
@ -331,7 +336,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) {
// todo: check delivery method
log.trace("Submitting notification request update: {}", update); log.trace("Submitting notification request update: {}", update);
wsCallBackExecutor.submit(() -> { wsCallBackExecutor.submit(() -> {
TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); 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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component @Component
@RequiredArgsConstructor @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.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; 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> { 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.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
@Component @Component
@RequiredArgsConstructor @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.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; 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; import org.thingsboard.server.service.sms.SmsExecutorService;
@Component @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 * See the License for the specific language governing permissions and
* limitations under the License. * 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.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
public interface NotificationRuleProcessingService { 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.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.notification.NotificationSchedulerService; 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.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -155,9 +156,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
GitVersionControlQueueService vcQueueService, GitVersionControlQueueService vcQueueService,
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
Optional<JwtSettingsService> jwtSettingsService, Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) { NotificationSchedulerService notificationSchedulerService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();

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.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; 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.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
@ -128,8 +129,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService, TbApiUsageStateService apiUsageStateService,
PartitionService partitionService, ApplicationEventPublisher eventPublisher, PartitionService partitionService, ApplicationEventPublisher eventPublisher,
NotificationRuleProcessingService notificationRuleProcessingService,
TbServiceInfoProvider serviceInfoProvider, QueueService queueService) { TbServiceInfoProvider serviceInfoProvider, QueueService queueService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty());
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory; this.submitStrategyFactory = submitStrategyFactory;
@ -480,6 +482,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
} }
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage()); msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage());
actorContext.tell(msg); actorContext.tell(msg);
notificationRuleProcessingService.process(tenantId, tbMsg);
} }
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}") @Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}")

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

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

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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; 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.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService; 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.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmSubscriptionUpdateProto; 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.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
@ -297,7 +295,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm, s -> alarm,
false false
); );
notificationRuleProcessingService.onAlarmCreatedOrUpdated(tenantId, alarm); notificationRuleProcessingService.process(tenantId, alarm, false);
callback.onSuccess(); callback.onSuccess();
} }
@ -315,7 +313,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
s -> alarm, s -> alarm,
true true
); );
notificationRuleProcessingService.onAlarmDeleted(tenantId, alarm); notificationRuleProcessingService.process(tenantId, alarm, true);
callback.onSuccess(); callback.onSuccess();
} }
@ -345,15 +343,22 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
@Override @Override
public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) { public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate); NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate);
subscriptionsByEntityId.entrySet().stream() subscriptionsByEntityId.forEach((entityId, subscriptions) -> {
.filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER) if (entityId.getEntityType() != EntityType.USER) {
.flatMap(subEntry -> subEntry.getValue().stream() return;
.filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS }
|| sub.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) subscriptions.forEach(subscription -> {
.filter(sub -> sub.getServiceId().equals(serviceId))) if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS &&
.forEach(subscription -> { subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); return;
}); }
if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) {
return;
}
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(),
subscriptionUpdate, TbCallback.EMPTY);
});
});
callback.onSuccess(); callback.onSuccess();
} }
@ -386,7 +391,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
deleteDeviceInactivityTimeout(tenantId, entityId, keys); deleteDeviceInactivityTimeout(tenantId, entityId, keys);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId, clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId,
new DeviceId(entityId.getId()), scope, keys), null); new DeviceId(entityId.getId()), scope, keys), null);
} }
} }
callback.onSuccess(); 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.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
@ -175,21 +174,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
NotificationRequestId notificationRequestId = update.getNotificationRequestId(); fetchUnreadNotifications(subscription); // FIXME: figure out how not to fetch notifications on each request update...
if (update.isDeleted()) { sendUpdate(subscription.getSessionId(), subscription.createFullUpdate());
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));
});
}
} }
@ -230,10 +216,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) {
log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
if (update.isDeleted()) { fetchUnreadNotificationsCount(subscription); // FIXME: figure out how not to fetch notifications on each request update...
fetchUnreadNotificationsCount(subscription); sendUpdate(subscription.getSessionId(), subscription.createUpdate());
sendUpdate(subscription.getSessionId(), subscription.createUpdate());
}
} }

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 @Builder
public class NotificationRequestUpdate { public class NotificationRequestUpdate {
private NotificationRequestId notificationRequestId; private NotificationRequestId notificationRequestId;
private String notificationReason;
private NotificationInfo notificationInfo; private NotificationInfo notificationInfo;
private boolean deleted; private boolean deleted;
} }

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

@ -433,6 +433,9 @@ cache:
assetProfiles: assetProfiles:
timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}"
maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}" 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: attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
@ -1204,6 +1207,9 @@ vc:
io_pool_size: "${TB_VC_GIT_POOL_SIZE:3}" io_pool_size: "${TB_VC_GIT_POOL_SIZE:3}"
repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}" repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}"
notification_system:
thread_pool_size: "${TB_NOTIFICATION_SYSTEM_THREAD_POOL_SIZE:30}"
management: management:
endpoints: endpoints:
web: 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; return wsClient;
} }
protected void connectWsClient() throws Exception { @Override
loginCustomerUser(); public NotificationApiWsClient getWsClient() {
wsClient = (NotificationApiWsClient) super.getWsClient(); return (NotificationApiWsClient) super.getWsClient();
loginTenantAdmin();
}
protected void connectOtherWsClient() throws Exception {
loginCustomerUser();
otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient();
loginTenantAdmin();
} }
} }

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

@ -90,7 +90,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Before @Before
public void beforeEach() throws Exception { public void beforeEach() throws Exception {
connectWsClient(); loginCustomerUser();
wsClient = getWsClient();
loginTenantAdmin();
} }
@Test @Test
@ -274,7 +276,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationRequest.setInfo(newNotificationInfo); notificationRequest.setInfo(newNotificationInfo);
notificationCenter.updateNotificationRequest(tenantId, notificationRequest); notificationCenter.updateNotificationRequest(tenantId, notificationRequest);
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().iterator().next();
assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo); assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo);
assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification); assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification);
} }
@ -583,4 +585,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(notificationsUpdate.getTotalUnreadCount()).isEqualTo(expectedUnreadCount); 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)); super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token));
} }
public void subscribeForUnreadNotifications(int limit) { public NotificationApiWsClient subscribeForUnreadNotifications(int limit) {
NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit)); cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit));
sendCmd(cmdsWrapper); sendCmd(cmdsWrapper);
this.limit = limit; this.limit = limit;
return this;
} }
public void subscribeForUnreadNotificationsCount() { public NotificationApiWsClient subscribeForUnreadNotificationsCount() {
NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper();
cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2)); cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2));
sendCmd(cmdsWrapper); sendCmd(cmdsWrapper);
return this;
} }
public void markNotificationAsRead(UUID... notifications) { 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.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; 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.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; 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.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.NotificationRuleId; 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.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationType; 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.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.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -58,14 +62,16 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; 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.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
public class NotificationRuleApiTest extends AbstractNotificationApiTest { public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@ -79,40 +85,98 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
} }
@Test @Test
public void testNotificationRuleProcessing() throws Exception { public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL}; String notificationSubject = "${msgType}: ${originatorType} [${originatorId}]";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, "New alarm", "NEW ALARM ${alarmType}", deliveryMethods); String notificationText = "User: ${userName}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
notificationRule.setTenantId(tenantId); notificationRule.setName("Push-notification when any device is created, updated or deleted");
notificationRule.setName("Test rule for my alarms");
notificationRule.setTemplateId(notificationTemplate.getId()); notificationRule.setTemplateId(notificationTemplate.getId());
notificationRule.setDeliveryMethods(List.of(deliveryMethods)); notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
NotificationRuleConfig config = new NotificationRuleConfig();
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<>(); Map<Integer, NotificationApiWsClient> clients = new HashMap<>();
for (int delay = 0; delay <= 5; delay++) { for (int delay = 0; delay <= 5; delay++) {
Pair<User, NotificationApiWsClient> userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN); Pair<User, NotificationApiWsClient> userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN);
NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId()); NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId());
escalationTable.put(delay, List.of(notificationTarget.getUuidId()));
NotificationEscalation escalation = new NotificationEscalation();
escalation.setDelayInSec(delay);
escalation.setNotificationTargets(List.of(notificationTarget.getId()));
escalations.add(escalation);
clients.put(delay, userAndClient.getSecond()); clients.put(delay, userAndClient.getSecond());
} }
notificationRule.setRecipientsConfig(recipientsConfig);
config.setEscalations(escalations);
notificationRule.setConfiguration(config);
notificationRule = saveNotificationRule(notificationRule); notificationRule = saveNotificationRule(notificationRule);
String alarmType = "boolIsTrue";
String alarmType = "myBoolIsTrue";
DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType); DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType);
Device device = createDevice("Device 1", deviceProfile.getName(), "1234"); Device device = createDevice("Device 1", deviceProfile.getName(), "1234");
clients.values().forEach(wsClient -> { clients.values().forEach(wsClient -> {
wsClient.subscribeForUnreadNotifications(10); wsClient.subscribeForUnreadNotifications(10).waitForReply(true);
wsClient.waitForReply();
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
}); });
@ -122,9 +186,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType))); verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType)));
Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get(); 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) await().atMost(7, TimeUnit.SECONDS)
.until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null)); .until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null));
clients.forEach((expectedDelay, wsClient) -> { clients.forEach((expectedDelay, wsClient) -> {
@ -132,27 +195,88 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
double actualDelay = (double) (notification.getCreatedTime() - ts) / 1000; double actualDelay = (double) (notification.getCreatedTime() - ts) / 1000;
assertThat(actualDelay).isCloseTo(expectedDelay, offset(0.5)); 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.getType()).isEqualTo(NotificationType.ALARM);
assertThat(notification.getSubject()).isEqualTo("New alarm");
assertThat(notification.getInfo()).isInstanceOf(AlarmOriginatedNotificationInfo.class); assertThat(notification.getInfo()).isInstanceOf(AlarmOriginatedNotificationInfo.class);
AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo(); AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo();
assertThat(info.getAlarmId()).isEqualTo(alarm.getId()); assertThat(info.getAlarmId()).isEqualTo(alarm.getId());
assertThat(info.getAlarmType()).isEqualTo(alarm.getType()); assertThat(info.getAlarmType()).isEqualTo(alarmType);
assertThat(info.getAlarmSeverity()).isEqualTo(AlarmSeverity.CRITICAL); assertThat(info.getAlarmSeverity()).isEqualTo(expectedSeverity);
assertThat(info.getAlarmStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK); assertThat(info.getAlarmStatus()).isEqualTo(expectedStatus);
}); });
clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate()); clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate());
alarmSubscriptionService.ackAlarm(tenantId, alarm.getId(), System.currentTimeMillis()); alarmSubscriptionService.ackAlarm(tenantId, alarm.getId(), System.currentTimeMillis());
AlarmStatus expectedStatus = AlarmStatus.ACTIVE_ACK;
AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL;
clients.values().forEach(wsClient -> { clients.values().forEach(wsClient -> {
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().stream().findFirst().get();
assertThat(((AlarmOriginatedNotificationInfo) updatedNotification.getInfo()).getAlarmStatus()) assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " +
.isEqualTo(AlarmStatus.ACTIVE_ACK); "severity: " + expectedSeverity + ", deviceId: " + device.getId());
assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity);
wsClient.close(); 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) { 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.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface NotificationRuleService { public interface NotificationRuleService {
NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule); NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule);
@ -29,6 +32,8 @@ public interface NotificationRuleService {
PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink); PageData<NotificationRule> findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink);
List<NotificationRule> findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);
void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id); 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; package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -36,6 +38,8 @@ public interface NotificationService {
int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId);
void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status);
boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId); 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_CACHE = "tenants";
public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String NOTIFICATION_RULES_CACHE = "notificationRules";
public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes"; public static final String ATTRIBUTES_CACHE = "attributes";

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. " + "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.") "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 List<String> propagateRelationTypes;
private NotificationRuleId notificationRuleId;
public Alarm() { public Alarm() {
super(); super();
@ -109,7 +108,6 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.propagateToOwner = alarm.isPropagateToOwner(); this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant(); this.propagateToTenant = alarm.isPropagateToTenant();
this.propagateRelationTypes = alarm.getPropagateRelationTypes(); this.propagateRelationTypes = alarm.getPropagateRelationTypes();
this.notificationRuleId = alarm.getNotificationRuleId();
} }
@Override @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; package org.thingsboard.server.common.data.notification;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -43,4 +44,14 @@ public class Notification extends BaseData<NotificationId> {
private NotificationStatus status; private NotificationStatus status;
@JsonProperty("text")
public String getProcessedText() {
return NotificationProcessingContext.processTemplate(text, info);
}
@JsonProperty("subject")
public String getProcessedSubject() {
return NotificationProcessingContext.processTemplate(subject, info);
}
} }

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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.common.data.notification;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang3.StringUtils; 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.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.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.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.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
@ -91,7 +88,7 @@ public class NotificationProcessingContext {
} }
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) { public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
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 = new HashMap<>(templateContext);
templateContext.putAll(request.getInfo().getTemplateData()); templateContext.putAll(request.getInfo().getTemplateData());
} }
@ -105,8 +102,19 @@ public class NotificationProcessingContext {
return template; return template;
} }
private <T extends DeliveryMethodNotificationTemplate> String processTemplate(String template, Map<String, String> context) { private static String processTemplate(String template, Map<String, String> context) {
return TbNodeUtils.processTemplate(template, 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) { 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( return Map.of(
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.toString(),
"alarmStatus", alarmStatus.toString(),
"alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(),
"alarmOriginatorId", alarmOriginator.getId().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({ @JsonSubTypes({
@Type(name = "USER", value = UserOriginatedNotificationInfo.class), @Type(name = "USER", value = UserOriginatedNotificationInfo.class),
@Type(name = "ALARM", value = AlarmOriginatedNotificationInfo.class), @Type(name = "ALARM", value = AlarmOriginatedNotificationInfo.class),
@Type(name = "RULE_NODE", value = RuleNodeOriginatedNotificationInfo.class) @Type(name = "RULE_CHAIN", value = RuleEngineOriginatedNotificationInfo.class)
}) })
public interface NotificationInfo { 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; package org.thingsboard.server.common.data.notification.info;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Data @Data
public class RuleNodeOriginatedNotificationInfo implements NotificationInfo { @AllArgsConstructor
@NoArgsConstructor
@Builder
public class RuleEngineOriginatedNotificationInfo implements NotificationInfo {
private EntityId msgOriginator; private EntityId msgOriginator;
private String msgType;
private Map<String, String> msgMetadata; private Map<String, String> msgMetadata;
@Override @Override
public EntityType getOriginatorType() { public EntityType getOriginatorType() {
return EntityType.RULE_NODE; return EntityType.RULE_CHAIN;
} }
@Override @Override
public Map<String, String> getTemplateData() { 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; package org.thingsboard.server.common.data.notification.rule;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
@Data @Data
public class NotificationRuleConfig { @EqualsAndHashCode(callSuper = true)
public class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig {
@NotEmpty @NotEmpty
@Valid private List<UUID> targets;
private List<NotificationEscalation> escalations;
@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; package org.thingsboard.server.common.data.notification.rule;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.NotificationTargetId; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
@Data @Data
public class NotificationEscalation { @EqualsAndHashCode(callSuper = true)
public class EscalatedNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig {
@Max(NotificationRequestConfig.MAX_SENDING_DELAY)
private int delayInSec;
@NotEmpty @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; package org.thingsboard.server.common.data.notification.rule;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId; 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.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; 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.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -41,10 +41,20 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private String name; private String name;
@NotNull @NotNull
private NotificationTemplateId templateId; private NotificationTemplateId templateId;
@NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods; @NotNull
private NotificationRuleTriggerType triggerType;
@NotNull
private NotificationRuleTriggerConfig triggerConfig;
@NotNull @NotNull
@Valid @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_TABLE_NAME = "notification_rule";
public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; 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_TRIGGER_TYPE_PROPERTY = "trigger_type";
public static final String NOTIFICATION_RULE_CONFIGURATION_PROPERTY = "configuration"; 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_TABLE_NAME = "notification_template";
public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type"; 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 { } else {
this.propagateRelationTypes = null; this.propagateRelationTypes = null;
} }
this.notificationRuleId = getUuid(alarm.getNotificationRuleId());
} }
public AbstractAlarmEntity(AlarmEntity alarmEntity) { public AbstractAlarmEntity(AlarmEntity alarmEntity) {
@ -178,7 +177,6 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.clearTs = alarmEntity.getClearTs(); this.clearTs = alarmEntity.getClearTs();
this.details = alarmEntity.getDetails(); this.details = alarmEntity.getDetails();
this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes(); this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes();
this.notificationRuleId = alarmEntity.getNotificationRuleId();
} }
protected Alarm toAlarm() { protected Alarm toAlarm() {
@ -207,7 +205,6 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
} else { } else {
alarm.setPropagateRelationTypes(Collections.emptyList()); alarm.setPropagateRelationTypes(Collections.emptyList());
} }
alarm.setNotificationRuleId(getEntityId(notificationRuleId, NotificationRuleId::new));
return alarm; 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.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; 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.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.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.UUID; import java.util.UUID;
@ -49,12 +52,17 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false)
private UUID templateId; private UUID templateId;
@Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false) @Enumerated(EnumType.STRING)
private String deliveryMethods; @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY, nullable = false)
private NotificationRuleTriggerType triggerType;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode triggerConfig;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY, nullable = false)
private JsonNode recipientsConfig;
public NotificationRuleEntity() {} public NotificationRuleEntity() {}
@ -64,8 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setTenantId(getTenantUuid(notificationRule.getTenantId())); setTenantId(getTenantUuid(notificationRule.getTenantId()));
setName(notificationRule.getName()); setName(notificationRule.getName());
setTemplateId(getUuid(notificationRule.getTemplateId())); setTemplateId(getUuid(notificationRule.getTemplateId()));
setDeliveryMethods(listToString(notificationRule.getDeliveryMethods())); setTriggerType(notificationRule.getTriggerType());
setConfiguration(toJson(notificationRule.getConfiguration())); setTriggerConfig(toJson(notificationRule.getTriggerConfig()));
setRecipientsConfig(toJson(notificationRule.getRecipientsConfig()));
} }
@Override @Override
@ -76,8 +85,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setTenantId(getTenantId(tenantId)); notificationRule.setTenantId(getTenantId(tenantId));
notificationRule.setName(name); notificationRule.setName(name);
notificationRule.setTemplateId(getEntityId(templateId, NotificationTemplateId::new)); notificationRule.setTemplateId(getEntityId(templateId, NotificationTemplateId::new));
notificationRule.setDeliveryMethods(listFromString(deliveryMethods, NotificationDeliveryMethod::valueOf)); notificationRule.setTriggerType(triggerType);
notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class)); notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class));
notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class));
return notificationRule; return notificationRule;
} }

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

@ -64,6 +64,7 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
@Override @Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) {
// FIXME: add caching
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId); 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.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue;
import java.util.List;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationRuleService implements NotificationRuleService { public class DefaultNotificationRuleService extends AbstractCachedEntityService<NotificationRuleCacheKey, NotificationRuleCacheValue, NotificationRule> implements NotificationRuleService {
private final NotificationRuleDao notificationRuleDao; private final NotificationRuleDao notificationRuleDao;
@Override @Override
public NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule) { 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 @Override
@ -44,9 +64,32 @@ public class DefaultNotificationRuleService implements NotificationRuleService {
return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink); 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 @Override
public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) { public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) {
NotificationRule notificationRule = findNotificationRuleById(tenantId, id);
publishEvictEvent(notificationRule);
notificationRuleDao.removeById(tenantId, id.getId()); 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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
@ -71,6 +72,11 @@ public class DefaultNotificationService implements NotificationService {
return notificationDao.countUnreadByUserId(tenantId, userId); return notificationDao.countUnreadByUserId(tenantId, userId);
} }
@Override
public void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) {
notificationDao.updateStatusesByRequestId(tenantId, requestId, status);
}
@Override @Override
public boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId) { public boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId) {
return notificationDao.deleteByIdAndUserId(tenantId, userId, 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); 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); 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.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface NotificationRuleDao extends Dao<NotificationRule> { public interface NotificationRuleDao extends Dao<NotificationRule> {
PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationRule> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId); 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))); 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 @Override
public boolean deleteByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId) { public boolean deleteByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId) {
return notificationRepository.deleteByIdAndRecipientId(notificationId.getId(), userId.getId()) != 0; 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.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; 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.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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil; 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.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.getId; import static org.thingsboard.server.dao.DaoUtil.getId;
@ -50,7 +52,12 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
@Override @Override
public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) { 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 @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); 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); 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.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
@ -28,6 +30,8 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
Page<NotificationRuleEntity> findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); 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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; 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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
@ -102,9 +101,6 @@ public class AlarmDataAdapter {
} else { } else {
alarm.setPropagateRelationTypes(Collections.emptyList()); 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); UUID entityUuid = (UUID) row.get(ModelConstants.ENTITY_ID_COLUMN);
EntityId entityId = entityIdMap.get(entityUuid); EntityId entityId = entityIdMap.get(entityUuid);
Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY); 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, tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255) NOT NULL, trigger_type VARCHAR(50) NOT NULL,
configuration VARCHAR(2000) NOT NULL trigger_config VARCHAR(1000) NOT NULL,
recipients_config VARCHAR(10000) NOT NULL
); );
CREATE TABLE IF NOT EXISTS notification_request ( 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); NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text); 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 com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils; 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.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@ -184,7 +183,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
existingAlarm.setDetails(details); existingAlarm.setDetails(details);
} }
existingAlarm.setEndTs(System.currentTimeMillis()); existingAlarm.setEndTs(System.currentTimeMillis());
existingAlarm.setNotificationRuleId(config.getNotificationRuleId());
return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm); return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
}, ctx.getDbCallbackExecutor()); }, ctx.getDbCallbackExecutor());
@ -206,7 +204,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
.startTs(ts) .startTs(ts)
.endTs(ts) .endTs(ts)
.details(details) .details(details)
.notificationRuleId(config.getNotificationRuleId())
.build(); .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 lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; 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.script.ScriptLanguage;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@ -39,8 +38,6 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
private List<String> relationTypes; private List<String> relationTypes;
private NotificationRuleId notificationRuleId;
@Override @Override
public TbCreateAlarmNodeConfiguration defaultConfiguration() { public TbCreateAlarmNodeConfiguration defaultConfiguration() {
TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); 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.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; 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.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -50,7 +50,7 @@ public class TbNotificationNode implements TbNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
RuleNodeOriginatedNotificationInfo notificationInfo = new RuleNodeOriginatedNotificationInfo(); RuleEngineOriginatedNotificationInfo notificationInfo = new RuleEngineOriginatedNotificationInfo();
notificationInfo.setMsgOriginator(msg.getOriginator()); notificationInfo.setMsgOriginator(msg.getOriginator());
notificationInfo.setMsgMetadata(msg.getMetaData().getData()); notificationInfo.setMsgMetadata(msg.getMetaData().getData());
@ -60,7 +60,7 @@ public class TbNotificationNode implements TbNode {
.templateId(config.getTemplateId()) .templateId(config.getTemplateId())
.info(notificationInfo) .info(notificationInfo)
.additionalConfig(new NotificationRequestConfig()) .additionalConfig(new NotificationRequestConfig())
.originatorEntityId(ctx.getSelfId()) .originatorEntityId(ctx.getSelf().getRuleChainId())
.build(); .build();
DonAsynchron.withCallback(ctx.getNotificationExecutor().executeAsync(() -> { 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. // Skip update if severity is decreased.
if (severity.ordinal() <= oldSeverity.ordinal()) { if (severity.ordinal() <= oldSeverity.ordinal()) {
currentAlarm.setDetails(createDetails(ruleState)); currentAlarm.setDetails(createDetails(ruleState));
currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId());
if (!oldSeverity.equals(severity)) { if (!oldSeverity.equals(severity)) {
currentAlarm.setSeverity(severity); currentAlarm.setSeverity(severity);
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
@ -273,7 +272,6 @@ class AlarmState {
if (alarmDefinition.getPropagateRelationTypes() != null) { if (alarmDefinition.getPropagateRelationTypes() != null) {
currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes()); currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes());
} }
currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId());
currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm);
boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs(); boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs();
return new TbAlarmResult(!updated, updated, false, false, currentAlarm); return new TbAlarmResult(!updated, updated, false, false, currentAlarm);

Loading…
Cancel
Save