From b7f3aef044b1885b41d08f515fed69b73b216eb3 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 17 Jan 2023 16:30:47 +0200 Subject: [PATCH] Notification rules; refactoring --- .../main/data/upgrade/3.4.3/schema_update.sql | 5 +- .../controller/NotificationController.java | 2 +- .../NotificationExecutorService.java | 6 +- .../DefaultNotificationCenter.java | 32 +-- ...aultNotificationRuleProcessingService.java | 173 ------------- .../channels/EmailNotificationChannel.java | 2 +- .../channels/NotificationChannel.java | 2 +- .../channels/SlackNotificationChannel.java | 2 +- .../channels/SmsNotificationChannel.java | 2 +- ...aultNotificationRuleProcessingService.java | 233 ++++++++++++++++++ .../NotificationRuleProcessingService.java | 8 +- ...AlarmNotificationRuleTriggerProcessor.java | 49 ++++ ...ivityNotificationRuleTriggerProcessor.java | 53 ++++ ...ctionNotificationRuleTriggerProcessor.java | 53 ++++ .../NotificationRuleTriggerProcessor.java | 31 +++ .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultTbRuleEngineConsumerService.java | 5 +- .../processing/AbstractConsumerService.java | 4 + .../DefaultSubscriptionManagerService.java | 35 +-- .../DefaultNotificationCommandsHandler.java | 24 +- .../sub/NotificationRequestUpdate.java | 1 - .../src/main/resources/thingsboard.yml | 6 + .../AbstractNotificationApiTest.java | 13 +- .../notification/NotificationApiTest.java | 12 +- .../notification/NotificationApiWsClient.java | 6 +- .../notification/NotificationRuleApiTest.java | 190 +++++++++++--- .../notification/NotificationRuleService.java | 5 + .../dao/notification/NotificationService.java | 4 + .../server/common/data/CacheConstants.java | 1 + .../server/common/data/alarm/Alarm.java | 2 - .../data/notification/Notification.java | 11 + .../NotificationProcessingContext.java | 24 +- .../info/AlarmOriginatedNotificationInfo.java | 2 + .../notification/info/NotificationInfo.java | 2 +- ...RuleEngineOriginatedNotificationInfo.java} | 18 +- ...aultNotificationRuleRecipientsConfig.java} | 16 +- ...atedNotificationRuleRecipientsConfig.java} | 19 +- .../notification/rule/NotificationRule.java | 24 +- .../NotificationRuleRecipientsConfig.java | 45 ++++ .../AlarmNotificationRuleTriggerConfig.java | 41 +++ ...activityNotificationRuleTriggerConfig.java | 36 +++ ...tyActionNotificationRuleTriggerConfig.java | 34 +++ .../NotificationRuleTriggerConfig.java | 34 +++ .../trigger/NotificationRuleTriggerType.java | 31 +++ .../server/dao/model/ModelConstants.java | 5 +- .../dao/model/sql/AbstractAlarmEntity.java | 3 - .../dao/model/sql/NotificationRuleEntity.java | 30 ++- .../DefaultNotificationRequestService.java | 1 + .../DefaultNotificationRuleService.java | 47 +++- .../DefaultNotificationService.java | 6 + .../dao/notification/NotificationDao.java | 2 + .../dao/notification/NotificationRuleDao.java | 5 + .../cache/NotificationRuleCacheKey.java | 43 ++++ .../cache/NotificationRuleCacheValue.java | 37 +++ .../cache/NotificationRuleCaffeineCache.java | 32 +++ .../cache/NotificationRuleRedisCache.java | 35 +++ .../sql/notification/JpaNotificationDao.java | 5 + .../notification/JpaNotificationRuleDao.java | 9 +- .../notification/NotificationRepository.java | 7 + .../NotificationRuleRepository.java | 6 +- .../dao/sql/query/AlarmDataAdapter.java | 4 - .../main/resources/sql/schema-entities.sql | 5 +- .../rule/engine/api/NotificationCenter.java | 4 +- .../rule/engine/action/TbCreateAlarmNode.java | 3 - .../TbCreateAlarmNodeConfiguration.java | 3 - .../notification/TbNotificationNode.java | 6 +- .../rule/engine/profile/AlarmState.java | 2 - 67 files changed, 1246 insertions(+), 356 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java rename application/src/main/java/org/thingsboard/server/service/notification/{ => rule}/NotificationRuleProcessingService.java (74%) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java rename {application/src/main/java/org/thingsboard/server/service => common/data/src/main/java/org/thingsboard/server/common/data}/notification/NotificationProcessingContext.java (84%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/info/{RuleNodeOriginatedNotificationInfo.java => RuleEngineOriginatedNotificationInfo.java} (63%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/{NotificationRuleConfig.java => DefaultNotificationRuleRecipientsConfig.java} (69%) rename common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/{NotificationEscalation.java => EscalatedNotificationRuleRecipientsConfig.java} (65%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index f5817c3a2f..89f993b63e 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -39,8 +39,9 @@ CREATE TABLE IF NOT EXISTS notification_rule ( tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), - delivery_methods VARCHAR(255) NOT NULL, - configuration VARCHAR(2000) NOT NULL + trigger_type VARCHAR(50) NOT NULL, + trigger_config VARCHAR(1000) NOT NULL, + recipients_config VARCHAR(10000) NOT NULL ); CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 0ab6cdf898..c7c69659f0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestPreview; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; @@ -50,7 +51,6 @@ import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; diff --git a/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java index 15f1a27184..274dbae5d3 100644 --- a/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/executors/NotificationExecutorService.java @@ -15,15 +15,19 @@ */ package org.thingsboard.server.service.executors; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class NotificationExecutorService extends AbstractListeningExecutor { + @Value("${notification_system.thread_pool_size}") + private int notificationSystemExecutorThreadPoolSize; + @Override protected int getThreadPollSize() { - return 10; // FIXME [viacheslav] + return notificationSystemExecutorThreadPoolSize; } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index e8edac2e6c..c36ca25e24 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.AlreadySentException; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestStats; @@ -92,7 +93,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets()); notificationRequest.setTenantId(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId()); @@ -120,6 +120,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } + log.debug("Processing notification request (tenant id: {}, notification targets: {})", tenantId, notificationRequest.getTargets()); notificationRequest.setStatus(NotificationRequestStatus.PROCESSING); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); @@ -282,11 +283,27 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } + @Override + public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { + log.debug("Updating notification request {}", notificationRequest.getId()); + notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + // marking related notifications as unread: FIXME: causes each subscription to fetch notifications on each request update + notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT); + + onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() + .notificationRequestId(notificationRequest.getId()) + .notificationInfo(notificationRequest.getInfo()) + .deleted(false) + .build()); + return notificationRequest; + } + @Override public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { log.debug("Deleting notification request {}", notificationRequestId); NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);// TODO: add caching notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId); + // todo: check delivery method ? if (notificationRequest.isSent()) { onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequestId) @@ -296,18 +313,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); } - @Override - public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - log.debug("Updating notification request {}", notificationRequest.getId()); - notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); - onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() - .notificationRequestId(notificationRequest.getId()) - .notificationInfo(notificationRequest.getInfo()) - .deleted(false) - .build()); - return notificationRequest; - } - private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) @@ -331,7 +336,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { - // todo: check delivery method log.trace("Submitting notification request update: {}", update); wsCallBackExecutor.submit(() -> { TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java deleted file mode 100644 index 9a32a5d458..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ /dev/null @@ -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 onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm) { - return processAlarmUpdate(tenantId, alarm, false); - } - - @Override - public ListenableFuture onAlarmDeleted(TenantId tenantId, Alarm alarm) { - return processAlarmUpdate(tenantId, alarm, true); - } - - private ListenableFuture 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 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 scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId); - for (NotificationRequestId notificationRequestId : scheduledForRule) { - notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index 0b10ddfbc8..4616988cf1 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.service.mail.MailExecutorService; -import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java index f38fe94bdc..f1e172d2e5 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; -import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; public interface NotificationChannel { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index 2bd6c12785..eb4126bccb 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.service.executors.ExternalCallExecutorService; -import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; @Component @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java index 8bcbd7bca3..a4dceb851a 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; -import org.thingsboard.server.service.notification.NotificationProcessingContext; +import org.thingsboard.server.common.data.notification.NotificationProcessingContext; import org.thingsboard.server.service.sms.SmsExecutorService; @Component diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java new file mode 100644 index 0000000000..c13cae9ab8 --- /dev/null +++ b/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 triggerProcessors; + + private final NotificationExecutorService notificationExecutor; + private final DbCallbackExecutorService dbCallbackExecutor; + + private final Map 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 notificationInfoProvider) { + ListenableFuture> 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 void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, + T triggerObject, boolean triggerRemoved, + Supplier notificationInfoProvider) { + NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); + log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); + + if (triggerConfig.getTriggerType().isUpdatable()) { + List 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 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 scheduledForRule = notificationRequestService.findNotificationRequestsIdsByStatusAndRuleId(tenantId, NotificationRequestStatus.SCHEDULED, notificationRuleId); + for (NotificationRequestId notificationRequestId : scheduledForRule) { + notificationCenter.deleteNotificationRequest(tenantId, notificationRequestId); + } + }); + } + + @Autowired + public void setTriggerProcessors(Collection processors) { + this.triggerProcessors = processors.stream() + .collect(Collectors.toMap(NotificationRuleTriggerProcessor::getTriggerType, p -> p)); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java similarity index 74% rename from application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java index 8eb550e12a..94f0f1724f 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.notification; +package org.thingsboard.server.service.notification.rule; -import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; public interface NotificationRuleProcessingService { - ListenableFuture onAlarmCreatedOrUpdated(TenantId tenantId, Alarm alarm); + void process(TenantId tenantId, TbMsg ruleEngineMsg); - ListenableFuture onAlarmDeleted(TenantId tenantId, Alarm alarm); + void process(TenantId tenantId, Alarm alarm, boolean deleted); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..8e392b76bc --- /dev/null +++ b/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 { + + @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; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..abc84c2803 --- /dev/null +++ b/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 { + + 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; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..90326f86ef --- /dev/null +++ b/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 { + + @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; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java new file mode 100644 index 0000000000..ed28c4ba6c --- /dev/null +++ b/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 { + + boolean matchesFilter(T triggerObject, C triggerConfig); + + default boolean matchesClearRule(T triggerObject, C triggerConfig) { + return false; + } + + NotificationRuleTriggerType getTriggerType(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 09441f2563..73da54d20c 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -69,6 +69,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.notification.NotificationSchedulerService; +import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; @@ -155,9 +156,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService, NotificationSchedulerService notificationSchedulerService) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); + super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 1cc06eba1f..eaabae4560 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -52,6 +52,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; @@ -128,8 +129,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, PartitionService partitionService, ApplicationEventPublisher eventPublisher, + NotificationRuleProcessingService notificationRuleProcessingService, TbServiceInfoProvider serviceInfoProvider, QueueService queueService) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); + super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, notificationRuleProcessingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer(), Optional.empty()); this.statisticsService = statisticsService; this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; this.submitStrategyFactory = submitStrategyFactory; @@ -480,6 +482,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< } msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage()); actorContext.tell(msg); + notificationRuleProcessingService.process(tenantId, tbMsg); } @Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}") diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 0f5989bd1d..c1ea37c638 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.queue.TbQueueConsumer; @@ -77,6 +78,7 @@ public abstract class AbstractConsumerService> nfConsumer; protected final Optional jwtSettingsService; @@ -86,6 +88,7 @@ public abstract class AbstractConsumerService> nfConsumer, Optional jwtSettingsService) { this.actorContext = actorContext; this.encodingService = encodingService; @@ -95,6 +98,7 @@ public abstract class AbstractConsumerService alarm, false ); - notificationRuleProcessingService.onAlarmCreatedOrUpdated(tenantId, alarm); + notificationRuleProcessingService.process(tenantId, alarm, false); callback.onSuccess(); } @@ -315,7 +313,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene s -> alarm, true ); - notificationRuleProcessingService.onAlarmDeleted(tenantId, alarm); + notificationRuleProcessingService.process(tenantId, alarm, true); callback.onSuccess(); } @@ -345,15 +343,22 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene @Override public void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate notificationRequestUpdate, TbCallback callback) { NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationRequestUpdate); - subscriptionsByEntityId.entrySet().stream() - .filter(subEntry -> subEntry.getKey().getEntityType() == EntityType.USER) - .flatMap(subEntry -> subEntry.getValue().stream() - .filter(sub -> sub.getType() == TbSubscriptionType.NOTIFICATIONS - || sub.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) - .filter(sub -> sub.getServiceId().equals(serviceId))) - .forEach(subscription -> { - localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY); - }); + subscriptionsByEntityId.forEach((entityId, subscriptions) -> { + if (entityId.getEntityType() != EntityType.USER) { + return; + } + subscriptions.forEach(subscription -> { + if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS && + subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) { + return; + } + if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) { + return; + } + localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), + subscriptionUpdate, TbCallback.EMPTY); + }); + }); callback.onSuccess(); } @@ -386,7 +391,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene deleteDeviceInactivityTimeout(tenantId, entityId, keys); } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId, - new DeviceId(entityId.getId()), scope, keys), null); + new DeviceId(entityId.getId()), scope, keys), null); } } callback.onSuccess(); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 57dd925c88..3bfe888b7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -23,7 +23,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.NotificationId; -import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; @@ -175,21 +174,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void handleNotificationRequestUpdate(NotificationsSubscription subscription, NotificationRequestUpdate update) { log.trace("[{}, subId: {}] Handling notification request update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); - NotificationRequestId notificationRequestId = update.getNotificationRequestId(); - if (update.isDeleted()) { - if (subscription.getLatestUnreadNotifications().values().stream() - .anyMatch(notification -> notificationRequestId.equals(notification.getRequestId()))) { - fetchUnreadNotifications(subscription); - sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); - } - } else { - subscription.getLatestUnreadNotifications().values().stream() - .filter(notification -> notificationRequestId.equals(notification.getRequestId())) - .forEach(notification -> { - notification.setInfo(update.getNotificationInfo()); - sendUpdate(subscription.getSessionId(), subscription.createPartialUpdate(notification)); - }); - } + fetchUnreadNotifications(subscription); // FIXME: figure out how not to fetch notifications on each request update... + sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } @@ -230,10 +216,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void handleNotificationRequestUpdate(NotificationsCountSubscription subscription, NotificationRequestUpdate update) { log.trace("[{}, subId: {}] Handling notification request update for count sub: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); - if (update.isDeleted()) { - fetchUnreadNotificationsCount(subscription); - sendUpdate(subscription.getSessionId(), subscription.createUpdate()); - } + fetchUnreadNotificationsCount(subscription); // FIXME: figure out how not to fetch notifications on each request update... + sendUpdate(subscription.getSessionId(), subscription.createUpdate()); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java index aacde16ef7..4edc0c79c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationRequestUpdate.java @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo; @Builder public class NotificationRequestUpdate { private NotificationRequestId notificationRequestId; - private String notificationReason; private NotificationInfo notificationInfo; private boolean deleted; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ced616f42f..515661c8bb 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -433,6 +433,9 @@ cache: assetProfiles: timeToLiveInMinutes: "${CACHE_SPECS_ASSET_PROFILES_TTL:1440}" maxSize: "${CACHE_SPECS_ASSET_PROFILES_MAX_SIZE:10000}" + notificationRules: + timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}" + maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}" attributes: timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" @@ -1204,6 +1207,9 @@ vc: io_pool_size: "${TB_VC_GIT_POOL_SIZE:3}" repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}" +notification_system: + thread_pool_size: "${TB_NOTIFICATION_SYSTEM_THREAD_POOL_SIZE:30}" + management: endpoints: web: diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index 90d81af64c..3fa812151b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -197,16 +197,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest return wsClient; } - protected void connectWsClient() throws Exception { - loginCustomerUser(); - wsClient = (NotificationApiWsClient) super.getWsClient(); - loginTenantAdmin(); - } - - protected void connectOtherWsClient() throws Exception { - loginCustomerUser(); - otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient(); - loginTenantAdmin(); + @Override + public NotificationApiWsClient getWsClient() { + return (NotificationApiWsClient) super.getWsClient(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 820650d911..4d6864e187 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -90,7 +90,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Before public void beforeEach() throws Exception { - connectWsClient(); + loginCustomerUser(); + wsClient = getWsClient(); + loginTenantAdmin(); } @Test @@ -274,7 +276,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { notificationRequest.setInfo(newNotificationInfo); notificationCenter.updateNotificationRequest(tenantId, notificationRequest); wsClient.waitForUpdate(true); - Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); + Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().iterator().next(); assertThat(updatedNotification.getInfo()).isEqualTo(newNotificationInfo); assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(updatedNotification); } @@ -583,4 +585,10 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(notificationsUpdate.getTotalUnreadCount()).isEqualTo(expectedUnreadCount); } + protected void connectOtherWsClient() throws Exception { + loginCustomerUser(); + otherWsClient = (NotificationApiWsClient) super.getAnotherWsClient(); + loginTenantAdmin(); + } + } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index f266e763fa..7a7c835a6a 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -52,17 +52,19 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { super(new URI(wsUrl + "/api/ws/plugins/notifications?token=" + token)); } - public void subscribeForUnreadNotifications(int limit) { + public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); cmdsWrapper.setUnreadSubCmd(new NotificationsSubCmd(1, limit)); sendCmd(cmdsWrapper); this.limit = limit; + return this; } - public void subscribeForUnreadNotificationsCount() { + public NotificationApiWsClient subscribeForUnreadNotificationsCount() { NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); cmdsWrapper.setUnreadCountSubCmd(new NotificationsCountSubCmd(2)); sendCmd(cmdsWrapper); + return this; } public void markNotificationAsRead(UUID... notifications) { diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index f45397f845..c41f3082f7 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; @@ -37,13 +38,16 @@ import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationType; -import org.thingsboard.server.common.data.notification.rule.NotificationEscalation; +import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig; +import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig; import org.thingsboard.server.common.data.notification.rule.NotificationRule; -import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; @@ -58,14 +62,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.Offset.offset; +import static org.assertj.core.api.Assertions.offset; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest public class NotificationRuleApiTest extends AbstractNotificationApiTest { @@ -79,40 +85,98 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { } @Test - public void testNotificationRuleProcessing() throws Exception { - NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL}; - NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, "New alarm", "NEW ALARM ${alarmType}", deliveryMethods); + public void testNotificationRuleProcessing_entityActionTrigger() throws Exception { + String notificationSubject = "${msgType}: ${originatorType} [${originatorId}]"; + String notificationText = "User: ${userName}"; + NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH); NotificationRule notificationRule = new NotificationRule(); - notificationRule.setTenantId(tenantId); - notificationRule.setName("Test rule for my alarms"); + notificationRule.setName("Push-notification when any device is created, updated or deleted"); notificationRule.setTemplateId(notificationTemplate.getId()); - notificationRule.setDeliveryMethods(List.of(deliveryMethods)); - NotificationRuleConfig config = new NotificationRuleConfig(); + notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + + EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); + triggerConfig.setEntityType(EntityType.DEVICE); + triggerConfig.setCreated(true); + triggerConfig.setUpdated(true); + triggerConfig.setDeleted(true); + + DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + recipientsConfig.setTargets(List.of(createNotificationTarget(tenantAdminUserId).getUuidId())); + + notificationRule.setTriggerConfig(triggerConfig); + notificationRule.setRecipientsConfig(recipientsConfig); + notificationRule = saveNotificationRule(notificationRule); + + getWsClient().subscribeForUnreadNotifications(10).waitForReply(true); + - List 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> escalationTable = new HashMap<>(); + recipientsConfig.setEscalationTable(escalationTable); Map clients = new HashMap<>(); for (int delay = 0; delay <= 5; delay++) { Pair userAndClient = createUserAndConnectWsClient(Authority.TENANT_ADMIN); NotificationTarget notificationTarget = createNotificationTarget(userAndClient.getFirst().getId()); - - NotificationEscalation escalation = new NotificationEscalation(); - escalation.setDelayInSec(delay); - escalation.setNotificationTargets(List.of(notificationTarget.getId())); - escalations.add(escalation); + escalationTable.put(delay, List.of(notificationTarget.getUuidId())); clients.put(delay, userAndClient.getSecond()); } - - config.setEscalations(escalations); - notificationRule.setConfiguration(config); + notificationRule.setRecipientsConfig(recipientsConfig); notificationRule = saveNotificationRule(notificationRule); - String alarmType = "boolIsTrue"; + + + String alarmType = "myBoolIsTrue"; DeviceProfile deviceProfile = createDeviceProfileWithAlarmRules(notificationRule.getId(), alarmType); Device device = createDevice("Device 1", deviceProfile.getName(), "1234"); clients.values().forEach(wsClient -> { - wsClient.subscribeForUnreadNotifications(10); - wsClient.waitForReply(); + wsClient.subscribeForUnreadNotifications(10).waitForReply(true); wsClient.registerWaitForUpdate(); }); @@ -122,9 +186,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { verify(alarmSubscriptionService, timeout(2000)).createOrUpdateAlarm(argThat(alarm -> alarm.getType().equals(alarmType))); Alarm alarm = alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType).get(); - assertThat(alarm.getNotificationRuleId()).isEqualTo(notificationRule.getId()); - long ts = System.currentTimeMillis(); + long ts = System.currentTimeMillis(); await().atMost(7, TimeUnit.SECONDS) .until(() -> clients.values().stream().allMatch(client -> client.getLastDataUpdate() != null)); clients.forEach((expectedDelay, wsClient) -> { @@ -132,27 +195,88 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { double actualDelay = (double) (notification.getCreatedTime() - ts) / 1000; assertThat(actualDelay).isCloseTo(expectedDelay, offset(0.5)); - assertThat(notification.getText()).isEqualTo("NEW ALARM " + alarm.getType()); + AlarmStatus expectedStatus = AlarmStatus.ACTIVE_UNACK; + AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL; + + assertThat(notification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " + + "severity: " + expectedSeverity + ", deviceId: " + device.getId()); + assertThat(notification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity); + assertThat(notification.getType()).isEqualTo(NotificationType.ALARM); - assertThat(notification.getSubject()).isEqualTo("New alarm"); assertThat(notification.getInfo()).isInstanceOf(AlarmOriginatedNotificationInfo.class); AlarmOriginatedNotificationInfo info = (AlarmOriginatedNotificationInfo) notification.getInfo(); assertThat(info.getAlarmId()).isEqualTo(alarm.getId()); - assertThat(info.getAlarmType()).isEqualTo(alarm.getType()); - assertThat(info.getAlarmSeverity()).isEqualTo(AlarmSeverity.CRITICAL); - assertThat(info.getAlarmStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK); + assertThat(info.getAlarmType()).isEqualTo(alarmType); + assertThat(info.getAlarmSeverity()).isEqualTo(expectedSeverity); + assertThat(info.getAlarmStatus()).isEqualTo(expectedStatus); }); clients.values().forEach(wsClient -> wsClient.registerWaitForUpdate()); alarmSubscriptionService.ackAlarm(tenantId, alarm.getId(), System.currentTimeMillis()); + AlarmStatus expectedStatus = AlarmStatus.ACTIVE_ACK; + AlarmSeverity expectedSeverity = AlarmSeverity.CRITICAL; clients.values().forEach(wsClient -> { wsClient.waitForUpdate(true); - Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); - assertThat(((AlarmOriginatedNotificationInfo) updatedNotification.getInfo()).getAlarmStatus()) - .isEqualTo(AlarmStatus.ACTIVE_ACK); + Notification updatedNotification = wsClient.getLastDataUpdate().getNotifications().stream().findFirst().get(); + assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " + + "severity: " + expectedSeverity + ", deviceId: " + device.getId()); + assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity); wsClient.close(); }); + + // TODO: test clear rule + alarm not escalated + // TODO: test severity changes + } + + @Test + public void testNotificationRuleProcessing_alarmTrigger_clearRule() throws Exception { +/* + String notificationSubject = "New alarm '${alarmType}'"; + String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}"; + NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.PUSH); + + NotificationRule notificationRule = new NotificationRule(); + notificationRule.setName("Push-notification on any alarm"); + notificationRule.setTemplateId(notificationTemplate.getId()); + notificationRule.setTriggerType(NotificationRuleTriggerType.ALARM); + + AlarmNotificationRuleTriggerConfig triggerConfig = new AlarmNotificationRuleTriggerConfig(); + triggerConfig.setAlarmTypes(null); + triggerConfig.setAlarmSeverities(null); + notificationRule.setTriggerConfig(triggerConfig); + + EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM); + Map> escalationTable = new HashMap<>(); + recipientsConfig.setEscalationTable(escalationTable); + Map clients = new HashMap<>(); + for (int delay = 0; delay <= 5; delay++) { + Pair 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) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java index 5f3817fead..b8f60f704a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleService.java @@ -18,9 +18,12 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.util.List; + public interface NotificationRuleService { NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule); @@ -29,6 +32,8 @@ public interface NotificationRuleService { PageData findNotificationRulesByTenantId(TenantId tenantId, PageLink pageLink); + List findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType); + void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index c4e190171b..e034deede8 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -16,9 +16,11 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -36,6 +38,8 @@ public interface NotificationService { int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); + void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status); + boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index c6fbe3be5f..fb22af4435 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -29,6 +29,7 @@ public class CacheConstants { public static final String TENANTS_CACHE = "tenants"; public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; + public static final String NOTIFICATION_RULES_CACHE = "notificationRules"; public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 805c1e843c..37abba92a1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -81,7 +81,6 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") private List propagateRelationTypes; - private NotificationRuleId notificationRuleId; public Alarm() { super(); @@ -109,7 +108,6 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha this.propagateToOwner = alarm.isPropagateToOwner(); this.propagateToTenant = alarm.isPropagateToTenant(); this.propagateRelationTypes = alarm.getPropagateRelationTypes(); - this.notificationRuleId = alarm.getNotificationRuleId(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index f2ed3e8db4..cf987bc314 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.notification; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -43,4 +44,14 @@ public class Notification extends BaseData { private NotificationStatus status; + @JsonProperty("text") + public String getProcessedText() { + return NotificationProcessingContext.processTemplate(text, info); + } + + @JsonProperty("subject") + public String getProcessedSubject() { + return NotificationProcessingContext.processTemplate(subject, info); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java similarity index 84% rename from application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java index cf190423b6..a102f763a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java @@ -13,20 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.notification; +package org.thingsboard.server.common.data.notification; import com.google.common.base.Strings; import lombok.Builder; import lombok.Getter; import org.apache.commons.lang3.StringUtils; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; -import org.thingsboard.server.common.data.notification.NotificationRequest; -import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; @@ -91,7 +88,7 @@ public class NotificationProcessingContext { } public T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map templateContext) { - if (request.getInfo() != null) { + if (request.getInfo() != null && deliveryMethod != NotificationDeliveryMethod.PUSH) { // for push notifications we are processing template from info on each serialization templateContext = new HashMap<>(templateContext); templateContext.putAll(request.getInfo().getTemplateData()); } @@ -105,8 +102,19 @@ public class NotificationProcessingContext { return template; } - private String processTemplate(String template, Map context) { - return TbNodeUtils.processTemplate(template, context); + private static String processTemplate(String template, Map context) { + if (template == null || context.isEmpty()) return template; + String result = template; + for (Map.Entry 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 templateContext = notificationInfo.getTemplateData(); + return processTemplate(template, templateContext); } public Map createTemplateContext(User recipient) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmOriginatedNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmOriginatedNotificationInfo.java index 089de4c3df..98eeb2211e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmOriginatedNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmOriginatedNotificationInfo.java @@ -51,6 +51,8 @@ public class AlarmOriginatedNotificationInfo implements NotificationInfo { return Map.of( "alarmType", alarmType, "alarmId", alarmId.toString(), + "alarmSeverity", alarmSeverity.toString(), + "alarmStatus", alarmStatus.toString(), "alarmOriginatorEntityType", alarmOriginator.getEntityType().toString(), "alarmOriginatorId", alarmOriginator.getId().toString() ); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java index 9fddcefa10..3c60cd2330 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NotificationInfo.java @@ -29,7 +29,7 @@ import java.util.Map; @JsonSubTypes({ @Type(name = "USER", value = UserOriginatedNotificationInfo.class), @Type(name = "ALARM", value = AlarmOriginatedNotificationInfo.class), - @Type(name = "RULE_NODE", value = RuleNodeOriginatedNotificationInfo.class) + @Type(name = "RULE_CHAIN", value = RuleEngineOriginatedNotificationInfo.class) }) public interface NotificationInfo { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleNodeOriginatedNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java similarity index 63% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleNodeOriginatedNotificationInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java index 2301af15d5..63456852e7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleNodeOriginatedNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java @@ -15,26 +15,38 @@ */ package org.thingsboard.server.common.data.notification.info; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; +import java.util.HashMap; import java.util.Map; @Data -public class RuleNodeOriginatedNotificationInfo implements NotificationInfo { +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RuleEngineOriginatedNotificationInfo implements NotificationInfo { private EntityId msgOriginator; + private String msgType; private Map msgMetadata; @Override public EntityType getOriginatorType() { - return EntityType.RULE_NODE; + return EntityType.RULE_CHAIN; } @Override public Map getTemplateData() { - return msgMetadata; + Map templateData = new HashMap<>(msgMetadata); + templateData.put("originatorType", msgOriginator.getEntityType().toString()); + templateData.put("originatorId", msgOriginator.getId().toString()); + templateData.put("msgType", msgType); + return templateData; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java similarity index 69% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java index 8d7e353fab..06e6354748 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java @@ -16,17 +16,23 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; +import lombok.EqualsAndHashCode; -import javax.validation.Valid; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import java.util.List; +import java.util.Map; +import java.util.UUID; @Data -public class NotificationRuleConfig { +@EqualsAndHashCode(callSuper = true) +public class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { @NotEmpty - @Valid - private List escalations; + private List targets; + + @Override + public Map> getTargetsTable() { + return Map.of(0, targets); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java similarity index 65% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalation.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java index 85d44b31ab..03cf01d0da 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationEscalation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java @@ -16,20 +16,23 @@ package org.thingsboard.server.common.data.notification.rule; import lombok.Data; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import lombok.EqualsAndHashCode; -import javax.validation.constraints.Max; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import java.util.List; +import java.util.Map; +import java.util.UUID; @Data -public class NotificationEscalation { +@EqualsAndHashCode(callSuper = true) +public class EscalatedNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { - @Max(NotificationRequestConfig.MAX_SENDING_DELAY) - private int delayInSec; @NotEmpty - private List notificationTargets; + private Map> escalationTable; + + @Override + public Map> getTargetsTable() { + return escalationTable; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java index 3fb1e73c39..1a570c2b20 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java @@ -15,22 +15,22 @@ */ package org.thingsboard.server.common.data.notification.rule; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationRuleId; -import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import javax.validation.Valid; +import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @@ -41,10 +41,20 @@ public class NotificationRule extends BaseData implements Ha private String name; @NotNull private NotificationTemplateId templateId; - @NotEmpty - private List deliveryMethods; + + @NotNull + private NotificationRuleTriggerType triggerType; + @NotNull + private NotificationRuleTriggerConfig triggerConfig; @NotNull @Valid - private NotificationRuleConfig configuration; // todo: add pg_tgrm index (but index is 2.5x size of the column) + private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column) + + @JsonIgnore + @AssertTrue(message = "trigger type not matching") + public boolean isValid() { + return triggerType == triggerConfig.getTriggerType() && + triggerType == recipientsConfig.getTriggerType(); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java new file mode 100644 index 0000000000..6c45c1d572 --- /dev/null +++ b/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> getTargetsTable(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..d793eff877 --- /dev/null +++ b/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 alarmTypes; + private Set alarmSeverities; + private ClearRule clearRule; + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM; + } + + @Data + public static class ClearRule { + private AlarmStatus alarmStatus; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..99fd06d7ec --- /dev/null +++ b/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 devices; + private Set deviceProfiles; + // set either devices or profiles + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.DEVICE_INACTIVITY; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..6928b9f4c7 --- /dev/null +++ b/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; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..5c711ffd05 --- /dev/null +++ b/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(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java new file mode 100644 index 0000000000..2cb82ec07e --- /dev/null +++ b/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; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index fb0d69b66b..4819316391 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -674,8 +674,9 @@ public class ModelConstants { public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; - public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods"; - public static final String NOTIFICATION_RULE_CONFIGURATION_PROPERTY = "configuration"; + public static final String NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY = "trigger_type"; + public static final String NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY = "trigger_config"; + public static final String NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY = "recipients_config"; public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template"; public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index 4e1d5e1e75..2982132dec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -155,7 +155,6 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity } else { this.propagateRelationTypes = null; } - this.notificationRuleId = getUuid(alarm.getNotificationRuleId()); } public AbstractAlarmEntity(AlarmEntity alarmEntity) { @@ -178,7 +177,6 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.clearTs = alarmEntity.getClearTs(); this.details = alarmEntity.getDetails(); this.propagateRelationTypes = alarmEntity.getPropagateRelationTypes(); - this.notificationRuleId = alarmEntity.getNotificationRuleId(); } protected Alarm toAlarm() { @@ -207,7 +205,6 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity } else { alarm.setPropagateRelationTypes(Collections.emptyList()); } - alarm.setNotificationRuleId(getEntityId(notificationRuleId, NotificationRuleId::new)); return alarm; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java index dd4c729986..bca0627f76 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java @@ -22,15 +22,18 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTemplateId; -import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.rule.NotificationRule; -import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig; +import org.thingsboard.server.common.data.notification.rule.NotificationRuleRecipientsConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; @@ -49,12 +52,17 @@ public class NotificationRuleEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false) private UUID templateId; - @Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false) - private String deliveryMethods; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_TYPE_PROPERTY, nullable = false) + private NotificationRuleTriggerType triggerType; @Type(type = "json") - @Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false) - private JsonNode configuration; + @Column(name = ModelConstants.NOTIFICATION_RULE_TRIGGER_CONFIG_PROPERTY, nullable = false) + private JsonNode triggerConfig; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_RULE_RECIPIENTS_CONFIG_PROPERTY, nullable = false) + private JsonNode recipientsConfig; public NotificationRuleEntity() {} @@ -64,8 +72,9 @@ public class NotificationRuleEntity extends BaseSqlEntity { setTenantId(getTenantUuid(notificationRule.getTenantId())); setName(notificationRule.getName()); setTemplateId(getUuid(notificationRule.getTemplateId())); - setDeliveryMethods(listToString(notificationRule.getDeliveryMethods())); - setConfiguration(toJson(notificationRule.getConfiguration())); + setTriggerType(notificationRule.getTriggerType()); + setTriggerConfig(toJson(notificationRule.getTriggerConfig())); + setRecipientsConfig(toJson(notificationRule.getRecipientsConfig())); } @Override @@ -76,8 +85,9 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setTenantId(getTenantId(tenantId)); notificationRule.setName(name); notificationRule.setTemplateId(getEntityId(templateId, NotificationTemplateId::new)); - notificationRule.setDeliveryMethods(listFromString(deliveryMethods, NotificationDeliveryMethod::valueOf)); - notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class)); + notificationRule.setTriggerType(triggerType); + notificationRule.setTriggerConfig(fromJson(triggerConfig, NotificationRuleTriggerConfig.class)); + notificationRule.setRecipientsConfig(fromJson(recipientsConfig, NotificationRuleRecipientsConfig.class)); return notificationRule; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java index 90a7b5148a..e23eef9c14 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java @@ -64,6 +64,7 @@ public class DefaultNotificationRequestService implements NotificationRequestSer @Override public List findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { + // FIXME: add caching return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java index 0164684145..a7388e7988 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -20,18 +20,38 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; +import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey; +import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue; + +import java.util.List; @Service @RequiredArgsConstructor -public class DefaultNotificationRuleService implements NotificationRuleService { +public class DefaultNotificationRuleService extends AbstractCachedEntityService implements NotificationRuleService { private final NotificationRuleDao notificationRuleDao; @Override public NotificationRule saveNotificationRule(TenantId tenantId, NotificationRule notificationRule) { - return notificationRuleDao.save(tenantId, notificationRule); + boolean created = notificationRule.getId() == null; + if (!created) { + NotificationRule oldNotificationRule = findNotificationRuleById(tenantId, notificationRule.getId()); + if (notificationRule.getTriggerType() != oldNotificationRule.getTriggerType()) { + throw new IllegalArgumentException("Notification rule trigger type cannot be updated"); + } + } + try { + notificationRule = notificationRuleDao.saveAndFlush(tenantId, notificationRule); + publishEvictEvent(notificationRule); + } catch (Exception e) { + handleEvictEvent(notificationRule); + throw e; + } + return notificationRule; } @Override @@ -44,9 +64,32 @@ public class DefaultNotificationRuleService implements NotificationRuleService { return notificationRuleDao.findByTenantIdAndPageLink(tenantId, pageLink); } + @Override + public List findNotificationRulesByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType) { + NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder() + .tenantId(tenantId) + .triggerType(triggerType) + .build(); + return cache.getAndPutInTransaction(cacheKey, () -> NotificationRuleCacheValue.builder() + .notificationRules(notificationRuleDao.findByTenantIdAndTriggerType(tenantId, triggerType)) + .build(), false) + .getNotificationRules(); + } + @Override public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) { + NotificationRule notificationRule = findNotificationRuleById(tenantId, id); + publishEvictEvent(notificationRule); notificationRuleDao.removeById(tenantId, id.getId()); } + @Override + public void handleEvictEvent(NotificationRule notificationRule) { + NotificationRuleCacheKey cacheKey = NotificationRuleCacheKey.builder() + .tenantId(notificationRule.getTenantId()) + .triggerType(notificationRule.getTriggerType()) + .build(); + cache.evict(cacheKey); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index adea1ffdb3..0a073fb653 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; @@ -71,6 +72,11 @@ public class DefaultNotificationService implements NotificationService { return notificationDao.countUnreadByUserId(tenantId, userId); } + @Override + public void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status) { + notificationDao.updateStatusesByRequestId(tenantId, requestId, status); + } + @Override public boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId) { return notificationDao.deleteByIdAndUserId(tenantId, userId, notificationId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index 119f345ccb..910417017d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -37,6 +37,8 @@ public interface NotificationDao extends Dao { PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); + void updateStatusesByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status); + boolean deleteByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java index d0fa5fef8b..baf34b308c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java @@ -18,14 +18,19 @@ package org.thingsboard.server.dao.notification; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import java.util.List; + public interface NotificationRuleDao extends Dao { PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId); + List findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheKey.java new file mode 100644 index 0000000000..79a6ee3df9 --- /dev/null +++ b/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; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java b/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCacheValue.java new file mode 100644 index 0000000000..dc61bb27d7 --- /dev/null +++ b/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 notificationRules; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java new file mode 100644 index 0000000000..55c8f8a77d --- /dev/null +++ b/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 { + + public NotificationRuleCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.NOTIFICATION_RULES_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java new file mode 100644 index 0000000000..a8fb6f0d8a --- /dev/null +++ b/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 { + + public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) { + super(CacheConstants.NOTIFICATION_RULES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 6c84bc9075..43a87d1cbd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -88,6 +88,11 @@ public class JpaNotificationDao extends JpaAbstractDao findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType) { + return DaoUtil.convertDataList(notificationRuleRepository.findAllByTenantIdAndTriggerType(getId(tenantId, true), triggerType)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index b42727da26..a9d1b57d1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -48,6 +48,13 @@ public interface NotificationRepository extends JpaRepository 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); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java index 40777df351..4fcc22dd69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java @@ -19,8 +19,10 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; +import java.util.List; import java.util.UUID; @Repository @@ -28,6 +30,8 @@ public interface NotificationRuleRepository extends JpaRepository findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); - boolean existsByConfigurationContaining(String string); + boolean existsByRecipientsConfigContaining(String string); + + List findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java index b825d36892..ff156eb51c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; @@ -102,9 +101,6 @@ public class AlarmDataAdapter { } else { alarm.setPropagateRelationTypes(Collections.emptyList()); } - if (row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID) != null) { - alarm.setNotificationRuleId(new NotificationRuleId((UUID) row.get(ModelConstants.ALARM_NOTIFICATION_RULE_ID))); - } UUID entityUuid = (UUID) row.get(ModelConstants.ENTITY_ID_COLUMN); EntityId entityId = entityIdMap.get(entityUuid); Object originatorNameObj = row.get(ModelConstants.ALARM_ORIGINATOR_NAME_PROPERTY); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index b332aa4848..70d5f48d88 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -803,8 +803,9 @@ CREATE TABLE IF NOT EXISTS notification_rule ( tenant_id UUID NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), - delivery_methods VARCHAR(255) NOT NULL, - configuration VARCHAR(2000) NOT NULL + trigger_type VARCHAR(50) NOT NULL, + trigger_config VARCHAR(1000) NOT NULL, + recipients_config VARCHAR(10000) NOT NULL ); CREATE TABLE IF NOT EXISTS notification_request ( diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java index 58b4dde8ea..7113e481a8 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java @@ -25,10 +25,10 @@ public interface NotificationCenter { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); - void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); - NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); + void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index e27e5d2e5b..cb511eb3d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.EnumUtils; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -184,7 +183,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes; - private NotificationRuleId notificationRuleId; - @Override public TbCreateAlarmNodeConfiguration defaultConfiguration() { TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java index 07b4c3b74f..9f48ff1690 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java @@ -24,7 +24,7 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; -import org.thingsboard.server.common.data.notification.info.RuleNodeOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleEngineOriginatedNotificationInfo; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -50,7 +50,7 @@ public class TbNotificationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - RuleNodeOriginatedNotificationInfo notificationInfo = new RuleNodeOriginatedNotificationInfo(); + RuleEngineOriginatedNotificationInfo notificationInfo = new RuleEngineOriginatedNotificationInfo(); notificationInfo.setMsgOriginator(msg.getOriginator()); notificationInfo.setMsgMetadata(msg.getMetaData().getData()); @@ -60,7 +60,7 @@ public class TbNotificationNode implements TbNode { .templateId(config.getTemplateId()) .info(notificationInfo) .additionalConfig(new NotificationRequestConfig()) - .originatorEntityId(ctx.getSelfId()) + .originatorEntityId(ctx.getSelf().getRuleChainId()) .build(); DonAsynchron.withCallback(ctx.getNotificationExecutor().executeAsync(() -> { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 797e72d96e..1f90b4e6c1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -241,7 +241,6 @@ class AlarmState { // Skip update if severity is decreased. if (severity.ordinal() <= oldSeverity.ordinal()) { currentAlarm.setDetails(createDetails(ruleState)); - currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId()); if (!oldSeverity.equals(severity)) { currentAlarm.setSeverity(severity); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); @@ -273,7 +272,6 @@ class AlarmState { if (alarmDefinition.getPropagateRelationTypes() != null) { currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes()); } - currentAlarm.setNotificationRuleId(alarmDefinition.getNotificationRuleId()); currentAlarm = ctx.getAlarmService().createOrUpdateAlarm(currentAlarm); boolean updated = currentAlarm.getStartTs() != currentAlarm.getEndTs(); return new TbAlarmResult(!updated, updated, false, false, currentAlarm);