diff --git a/application/src/main/data/upgrade/3.4.2/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql similarity index 74% rename from application/src/main/data/upgrade/3.4.2/schema_update.sql rename to application/src/main/data/upgrade/3.4.3/schema_update.sql index 3dcc7c3a07..bee8454596 100644 --- a/application/src/main/data/upgrade/3.4.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -23,32 +23,39 @@ CREATE TABLE IF NOT EXISTS notification_target ( ); CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); +CREATE TABLE IF NOT EXISTS notification_template ( + id UUID NOT NULL CONSTRAINT notification_template_pkey PRIMARY KEY, + created_time BIGINT NOT NULL, + tenant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + configuration VARCHAR(10000) NOT NULL +); + CREATE TABLE IF NOT EXISTS notification_rule ( id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, name VARCHAR(255) NOT NULL, - notification_text_template VARCHAR NOT NULL, + template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), + delivery_methods VARCHAR(255), initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), escalation_config VARCHAR(500) ); -ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; - CREATE TABLE IF NOT EXISTS notification_request ( id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, created_time BIGINT NOT NULL, tenant_id UUID NOT NULL, target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), - notification_reason VARCHAR NOT NULL, - text_template VARCHAR NOT NULL, - notification_info VARCHAR(1000), - notification_severity VARCHAR(32), + type VARCHAR(255) NOT NULL, + template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), + info VARCHAR(1000), + delivery_methods VARCHAR(255), + additional_config VARCHAR(1000), originator_type VARCHAR(32) NOT NULL, originator_entity_id UUID, originator_entity_type VARCHAR(32), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), - additional_config VARCHAR(1000), status VARCHAR(32) ); CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_created_time ON notification_request(tenant_id, originator_type, created_time DESC); @@ -56,16 +63,18 @@ CREATE INDEX IF NOT EXISTS idx_notification_request_tenant_id_originator_type_cr CREATE TABLE IF NOT EXISTS notification ( id UUID NOT NULL, created_time BIGINT NOT NULL, - request_id UUID NOT NULL CONSTRAINT fk_notification_request_id - REFERENCES notification_request(id) ON DELETE CASCADE, - recipient_id UUID NOT NULL, - reason VARCHAR NOT NULL, - text VARCHAR NOT NULL, + request_id UUID NOT NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, + recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, + type VARCHAR(255) NOT NULL, + text VARCHAR(1000) NOT NULL, info VARCHAR(1000), - severity VARCHAR(32), originator_type VARCHAR(32) NOT NULL, status VARCHAR(32) ) PARTITION BY RANGE (created_time); CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC); CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id); + +ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; + +ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); 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 40d3c47fff..9064acb860 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -91,33 +91,23 @@ public class NotificationController extends BaseController { @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { // todo: check permission for notification target - if (notificationRequest.getId() == null) { - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); - notificationRequest.setOriginatorType(NotificationOriginatorType.USER); - notificationRequest.setOriginatorEntityId(user.getId()); - if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { - notificationRequest.setNotificationReason("General"); - } - if (notificationRequest.getNotificationSeverity() == null) { - notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); - } - if (notificationRequest.getNotificationInfo() != null && notificationRequest.getNotificationInfo().getOriginatorType() != null) { - throw new IllegalArgumentException("Unsupported notification info type"); - } - notificationRequest.setRuleId(null); - notificationRequest.setStatus(null); - return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); - } else { - NotificationRequest existingNotificationRequest = notificationRequestService.findNotificationRequestById(user.getTenantId(), notificationRequest.getId()); - checkNotNull(existingNotificationRequest); - accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.WRITE, notificationRequest.getId(), notificationRequest); + if (notificationRequest.getId() != null) { + throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it"); + } - existingNotificationRequest.setNotificationReason(notificationRequest.getNotificationReason()); - existingNotificationRequest.setTextTemplate(notificationRequest.getTextTemplate()); - existingNotificationRequest.setNotificationSeverity(notificationRequest.getNotificationSeverity()); - return notificationManager.updateNotificationRequest(user.getTenantId(), existingNotificationRequest); + accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); + notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); + notificationRequest.setOriginatorEntityId(user.getId()); + if (StringUtils.isBlank(notificationRequest.getType())) { + notificationRequest.setType("General"); + } + if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) { + throw new IllegalArgumentException("Unsupported notification info type"); } -// + notificationRequest.setRuleId(null); + notificationRequest.setStatus(null); + return notificationManager.processNotificationRequest(user.getTenantId(), notificationRequest); + // // try { // NotificationRequest savedNotificationRequest = ; // logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java index 5c01015f55..a343804921 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java @@ -16,21 +16,26 @@ package org.thingsboard.server.service.notification; import com.google.common.base.Strings; +import com.google.common.util.concurrent.Futures; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.User; 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.NotificationDeliveryMethod; 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.NotificationStatus; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -41,45 +46,37 @@ import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.NotificationsTopicService; -import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.notification.channels.NotificationChannel; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Future; +import java.util.stream.Collectors; @Service @Slf4j -public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager { +@RequiredArgsConstructor +public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager, NotificationChannel { private final NotificationTargetService notificationTargetService; private final NotificationRequestService notificationRequestService; private final NotificationService notificationService; + private final NotificationTemplateUtil notificationTemplateUtil; private final DbCallbackExecutorService dbCallbackExecutorService; private final NotificationsTopicService notificationsTopicService; private final TbQueueProducerProvider producerProvider; + private Map channels; - public DefaultNotificationManager(TbClusterService clusterService, PartitionService partitionService, - NotificationTargetService notificationTargetService, - NotificationRequestService notificationRequestService, - NotificationService notificationService, - DbCallbackExecutorService dbCallbackExecutorService, - NotificationsTopicService notificationsTopicService, - TbQueueProducerProvider producerProvider) { - super(clusterService, partitionService); - this.notificationTargetService = notificationTargetService; - this.notificationRequestService = notificationRequestService; - this.notificationService = notificationService; - this.dbCallbackExecutorService = dbCallbackExecutorService; - this.notificationsTopicService = notificationsTopicService; - this.producerProvider = producerProvider; - } @Override public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { @@ -98,17 +95,18 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); + Map textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods()); DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); }, 100, recipients -> { dbCallbackExecutorService.submit(() -> { - log.debug("Sending notifications for request {} to recipients batch", savedNotificationRequest.getId()); - for (User recipient : recipients) { - try { - Notification notification = createNotification(recipient, savedNotificationRequest); - onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); - } catch (Exception e) { - log.error("Failed to create notification for recipient {}", recipient.getId(), e); + for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) { + log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); + NotificationChannel notificationChannel = channels.get(deliveryMethod); + for (User recipient : recipients) { + Map templateContext = createTemplateContext(notificationRequest, recipient); + NotificationText text = notificationTemplateUtil.processTemplate(textTemplates.get(deliveryMethod), templateContext); + notificationChannel.sendNotification(recipient, savedNotificationRequest, text); } } }); @@ -117,6 +115,17 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl return savedNotificationRequest; } + private Map createTemplateContext(NotificationRequest notificationRequest, User recipient) { + Map templateContext = new HashMap<>(); + templateContext.put("email", recipient.getEmail()); + templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); + templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); + if (notificationRequest.getTemplateContext() != null) { + templateContext.putAll(notificationRequest.getTemplateContext()); + } + return templateContext; + } + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) @@ -131,6 +140,27 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); } + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId()); + Notification notification = Notification.builder() + .requestId(request.getId()) + .recipientId(recipient.getId()) + .type(request.getType()) + .text(text.getBody()) + .info(request.getInfo()) + .originatorType(request.getOriginatorType()) + .status(NotificationStatus.SENT) + .build(); + try { + notification = notificationService.saveNotification(recipient.getTenantId(), notification); + } catch (Exception e) { + log.error("Failed to create notification for recipient {}", recipient.getId(), e); + return Futures.immediateFailedFuture(e); + } + return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); + } + @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); @@ -158,52 +188,28 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) - .notificationInfo(notificationRequest.getNotificationInfo()) + .notificationInfo(notificationRequest.getInfo()) .deleted(false) .build()); return notificationRequest; } - private Notification createNotification(User recipient, NotificationRequest notificationRequest) { - log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), notificationRequest.getId()); - Notification notification = Notification.builder() - .requestId(notificationRequest.getId()) - .recipientId(recipient.getId()) - .reason(notificationRequest.getNotificationReason()) - .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) - .info(notificationRequest.getNotificationInfo()) - .severity(notificationRequest.getNotificationSeverity()) - .originatorType(notificationRequest.getOriginatorType()) - .status(NotificationStatus.SENT) - .build(); - return notificationService.saveNotification(recipient.getTenantId(), notification); - } - - private String formatNotificationText(String template, User recipient) { - Map context = Map.of( - "recipientEmail", recipient.getEmail(), - "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), - "recipientLastName", Strings.nullToEmpty(recipient.getLastName()) - ); - return TbNodeUtils.processTemplate(template, context); - } - - private void onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { + private Future onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) { NotificationUpdate update = NotificationUpdate.builder() .notification(notification) .isNew(isNew) .build(); log.trace("Submitting notification update for recipient {}: {}", recipientId, update); - wsCallBackExecutor.submit(() -> { + return wsCallBackExecutor.submit(() -> { forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); - }, () -> { - return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update); - }); + }, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update)); + return null; }); } 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); @@ -215,9 +221,20 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl }); } + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.WEBSOCKET; + } + @Override protected String getExecutorPrefix() { return "notification"; } + @Autowired + public void setChannels(List channels, NotificationManager websocketNotificationChannel) { + this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c)); + this.channels.put(NotificationDeliveryMethod.WEBSOCKET, (NotificationChannel) websocketNotificationChannel); + } + } 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 index a0e1dc1bb1..39bd325a99 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java @@ -19,9 +19,10 @@ 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.stereotype.Service; import org.thingsboard.rule.engine.api.NotificationManager; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -50,7 +51,8 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul private final NotificationRuleService notificationRuleService; private final NotificationRequestService notificationRequestService; - private final NotificationManager notificationManager; + @Autowired @Lazy + private NotificationManager notificationManager; private final DbCallbackExecutorService dbCallbackExecutorService; @Override @@ -108,9 +110,9 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul } else { NotificationInfo newNotificationInfo = constructNotificationInfo(alarm); for (NotificationRequest notificationRequest : notificationRequests) { - NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); + NotificationInfo previousNotificationInfo = notificationRequest.getInfo(); if (!previousNotificationInfo.equals(newNotificationInfo)) { - notificationRequest.setNotificationInfo(newNotificationInfo); + notificationRequest.setInfo(newNotificationInfo); notificationManager.updateNotificationRequest(tenantId, notificationRequest); } } @@ -127,34 +129,28 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul config.setSendingDelayInSec(delayInSec); } NotificationInfo notificationInfo = constructNotificationInfo(alarm); - + Map templateContext = Map.of( + "alarmType", alarm.getType(), + "alarmId", alarm.getId().toString(), + "alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(), + "alarmOriginatorId", alarm.getOriginator().getId().toString() + ); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .notificationReason("Alarm") - .textTemplate(formatNotificationTextTemplate(notificationRule.getNotificationTextTemplate(), alarm)) - .notificationInfo(notificationInfo) - .notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity + .type("Alarm") + .templateId(notificationRule.getTemplateId()) + .deliveryMethods(notificationRule.getDeliveryMethods()) + .additionalConfig(config) + .info(notificationInfo) .originatorType(NotificationOriginatorType.ALARM) .originatorEntityId(alarm.getId()) .ruleId(notificationRule.getId()) - .additionalConfig(config) + .templateContext(templateContext) .build(); notificationManager.processNotificationRequest(tenantId, notificationRequest); } - private String formatNotificationTextTemplate(String textTemplate, Alarm alarm) { - Map context = Map.of( // fixme: notification text is not updatable - "alarmType", alarm.getType(), - "alarmId", alarm.getId().toString(), - "alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(), - "alarmOriginatorId", alarm.getOriginator().getId().toString(), - "alarmSeverity", alarm.getSeverity().toString(), - "alarmStatus", alarm.getStatus().toString() - ); - return TbNodeUtils.processTemplate(textTemplate, context); - } - private NotificationInfo constructNotificationInfo(Alarm alarm) { return AlarmOriginatedNotificationInfo.builder() .alarmId(alarm.getId()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java index 2ae1ca43b6..a2757dc3dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java @@ -89,9 +89,8 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS .orElse(0); if (delayInSec <= 0) return; long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs); - if (delayInMs < 0) { // in case the scheduled request processing time was during the downtime + if (delayInMs < 0) { delayInMs = 0; - // or maybe no need to process outdated notification requests ? } ListenableScheduledFuture scheduledTask = scheduledExecutor.schedule(() -> { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java new file mode 100644 index 0000000000..b269712177 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java @@ -0,0 +1,56 @@ +/** + * 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 lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +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.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; +import org.thingsboard.server.dao.notification.NotificationTemplateService; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class NotificationTemplateUtil { + + private final NotificationTemplateService templateService; + + public Map getTemplates(TenantId tenantId, NotificationTemplateId templateId, List deliveryMethods) { + NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId); + NotificationTemplateConfig config = notificationTemplate.getConfiguration(); + return deliveryMethods.stream() + .collect(Collectors.toMap(k -> k, deliveryMethod -> { + return Optional.ofNullable(config.getTextTemplates()) + .map(templates -> templates.get(deliveryMethod)) + .orElse(config.getDefaultTextTemplate()); + })); + } + + public NotificationText processTemplate(NotificationTextTemplate template, Map templateContext) { + return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject()); + } + +} 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 new file mode 100644 index 0000000000..a3ec0fb2c3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.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.channels; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.service.mail.MailExecutorService; + +import java.util.concurrent.Future; + +@Component +@RequiredArgsConstructor +public class EmailNotificationChannel implements NotificationChannel { + + private final MailService mailService; + private final MailExecutorService executor; + + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + return executor.submit(() -> { + mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody()); + return null; + }); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.EMAIL; + } + +} 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 new file mode 100644 index 0000000000..ccf13bf2e8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.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.channels; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; + +import java.util.concurrent.Future; + +public interface NotificationChannel { + + Future sendNotification(User recipient, NotificationRequest request, NotificationText text); + + NotificationDeliveryMethod getDeliveryMethod(); + +} 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 new file mode 100644 index 0000000000..6ab263c779 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.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.channels; + +import com.google.common.util.concurrent.Futures; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +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.NotificationRequest; +import org.thingsboard.server.common.data.notification.template.NotificationText; +import org.thingsboard.server.service.sms.SmsExecutorService; + +import java.util.concurrent.Future; + +@Component +@RequiredArgsConstructor +public class SmsNotificationChannel implements NotificationChannel { + + private final SmsService smsService; + private final SmsExecutorService executor; + + @Override + public Future sendNotification(User recipient, NotificationRequest request, NotificationText text) { + String phone = recipient.getPhone(); + if (StringUtils.isBlank(phone)) return Futures.immediateFuture(null); + return executor.submit(() -> { + smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody()); + return null; + }); + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.SMS; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index d52c712074..6bd9b2f278 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -28,6 +28,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -149,7 +150,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService implements SubscriptionManagerService { - @Autowired - private AttributesService attrService; - - @Autowired - private TimeseriesService tsService; - - @Autowired - private NotificationsTopicService notificationsTopicService; - - @Autowired - private PartitionService partitionService; - - @Autowired - private TbServiceInfoProvider serviceInfoProvider; - - @Autowired - private TbQueueProducerProvider producerProvider; - - @Autowired - private TbLocalSubscriptionService localSubscriptionService; - - @Autowired - private DeviceStateService deviceStateService; - - @Autowired - private TbClusterService clusterService; - - @Autowired - private NotificationRuleProcessingService notificationRuleProcessingService; + private final AttributesService attrService; + private final TimeseriesService tsService; + private final NotificationsTopicService notificationsTopicService; + private final PartitionService partitionService; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbQueueProducerProvider producerProvider; + private final TbLocalSubscriptionService localSubscriptionService; + private final DeviceStateService deviceStateService; + private final TbClusterService clusterService; + private final NotificationRuleProcessingService notificationRuleProcessingService; private final Map> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index c0b58bf807..f33f8b833d 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -20,16 +20,17 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; -import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import javax.annotation.Nullable; @@ -51,23 +52,15 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList protected final Set currentPartitions = ConcurrentHashMap.newKeySet(); - protected final TbClusterService clusterService; - protected final PartitionService partitionService; + @Autowired + protected TbClusterService clusterService; + @Autowired + protected PartitionService partitionService; + @Autowired protected Optional subscriptionManagerService; protected ExecutorService wsCallBackExecutor; - public AbstractSubscriptionService(TbClusterService clusterService, - PartitionService partitionService) { - this.clusterService = clusterService; - this.partitionService = partitionService; - } - - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; - } - protected abstract String getExecutorPrefix(); @PostConstruct @@ -118,4 +111,5 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList } }, wsCallBackExecutor); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 3b8a72a21a..0833b42fd1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; @@ -55,28 +56,13 @@ import java.util.Optional; */ @Service @Slf4j +@RequiredArgsConstructor public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; - public DefaultAlarmSubscriptionService(TbClusterService clusterService, - PartitionService partitionService, - AlarmService alarmService, - TbApiUsageReportClient apiUsageClient, - TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); - this.alarmService = alarmService; - this.apiUsageClient = apiUsageClient; - this.apiUsageStateService = apiUsageStateService; - } - - @Autowired(required = false) - public void setSubscriptionManagerService(Optional subscriptionManagerService) { - this.subscriptionManagerService = subscriptionManagerService; - } - @Override protected String getExecutorPrefix() { return "alarm"; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 76a9582bd2..b2dbc3c2a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -83,11 +83,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, @Lazy TbEntityViewService tbEntityViewService, - TbClusterService clusterService, - PartitionService partitionService, TbApiUsageReportClient apiUsageClient, TbApiUsageStateService apiUsageStateService) { - super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.tbEntityViewService = tbEntityViewService; 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 cce7ff749e..163a98224c 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 @@ -232,12 +232,12 @@ public class NotificationApiTest extends AbstractControllerTest { getWsClient().waitForUpdate(true); Notification initialNotification = getWsClient().getLastDataUpdate().getUpdate(); assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification); - assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getNotificationInfo()); + assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getInfo()); getWsClient().registerWaitForUpdate(); NotificationInfo newNotificationInfo = new NotificationInfo(); newNotificationInfo.setDescription("New description"); - notificationRequest.setNotificationInfo(newNotificationInfo); + notificationRequest.setInfo(newNotificationInfo); notificationManager.updateNotificationRequest(tenantId, notificationRequest); getWsClient().waitForUpdate(true); Notification updatedNotification = getWsClient().getLastDataUpdate().getUpdate(); @@ -264,7 +264,7 @@ public class NotificationApiTest extends AbstractControllerTest { notificationTarget = saveNotificationTarget(notificationTarget); wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); - NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${recipientEmail}"); + NotificationRequest notificationRequest = submitNotificationRequest(notificationTarget.getId(), "Hello, ${email}"); await().atMost(5, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .until(() -> wsSessions.values().stream() @@ -339,9 +339,10 @@ public class NotificationApiTest extends AbstractControllerTest { NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targetId(targetId) - .notificationReason("Test") - .textTemplate(text) - .notificationInfo(notificationInfo) + .type("Test") + .templateId(notificationTemplate.getId()) + .info(notificationInfo) + .deliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET)) .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java index bcd6134478..9bf492a3e3 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java @@ -64,7 +64,7 @@ public class NotificationsClient extends NotificationApiWsClient { } SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); String time = format.format(new Date(notification.getCreatedTime())); - System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr); +// System.out.printf("[%s] %-19s | %-30s | (%s)\n", time, notification.getReason(), notification.getText(), notificationInfoStr); }); System.out.println(StringUtils.repeat(System.lineSeparator(), 5)); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java new file mode 100644 index 0000000000..c22e7a1caa --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.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.dao.notification; + +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.template.NotificationTemplate; + +import java.util.List; + +public interface NotificationTemplateService { + + NotificationTemplate findNotificationTemplateById(TenantId tenantId, NotificationTemplateId id); + + NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 60d8d7eca9..859f982d9c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -45,6 +45,8 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H @NoXss @Length(fieldName = "last name") private String lastName; + @NoXss + private String phone; public User() { super(); @@ -62,6 +64,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.authority = user.getAuthority(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.phone = user.getPhone(); } @@ -141,6 +144,14 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.lastName = lastName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + @ApiModelProperty(position = 10, value = "Additional parameters of the user", dataType = "com.fasterxml.jackson.databind.JsonNode") @Override public JsonNode getAdditionalInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java new file mode 100644 index 0000000000..596fe4b83c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java @@ -0,0 +1,30 @@ +/** + * 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.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class NotificationTemplateId extends UUIDBased { + + @JsonCreator + public NotificationTemplateId(@JsonProperty("id") UUID id) { + super(id); + } + +} 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 a39d858fcc..8cb879c178 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 @@ -34,10 +34,11 @@ public class Notification extends BaseData { private NotificationRequestId requestId; private UserId recipientId; - private String reason; + + private String type; // todo: maybe to enum private String text; private NotificationInfo info; - private NotificationSeverity severity; + private NotificationOriginatorType originatorType; private NotificationStatus status; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java index 2632d1b76e..3f19364afc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java @@ -15,6 +15,6 @@ */ package org.thingsboard.server.common.data.notification; -public class NotificationSettings { - // location on the screen, shown notifications count, timings of displaying +public enum NotificationDeliveryMethod { + WEBSOCKET, SMS, EMAIL, SLACK } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java index 44aa879a00..c02c7e47b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.notification; public enum NotificationOriginatorType { - USER, + ADMIN, ALARM, RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 142c3c6b9e..403391e385 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -28,12 +28,15 @@ 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.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @@ -47,39 +50,46 @@ public class NotificationRequest extends BaseData impleme private NotificationTargetId targetId; @NoXss - private String notificationReason; - @NotBlank(message = "Notification text template is missing") - private String textTemplate; // fixme: xss + private String type; + @NotNull + private NotificationTemplateId templateId; @Valid - private NotificationInfo notificationInfo; - private NotificationSeverity notificationSeverity; + private NotificationInfo info; + @NotEmpty + private List deliveryMethods; + @NotNull + @Valid + private NotificationRequestConfig additionalConfig; private NotificationOriginatorType originatorType; - private EntityId originatorEntityId; // userId, alarmId or tenantId + private EntityId originatorEntityId; private NotificationRuleId ruleId; - private NotificationRequestConfig additionalConfig; private NotificationRequestStatus status; + @JsonIgnore + private transient Map templateContext; + public NotificationRequest(NotificationRequest other) { super(other); this.tenantId = other.tenantId; this.targetId = other.targetId; - this.notificationReason = other.notificationReason; - this.textTemplate = other.textTemplate; - this.notificationInfo = other.notificationInfo; - this.notificationSeverity = other.notificationSeverity; + this.type = other.type; + this.templateId = other.templateId; + this.info = other.info; + this.deliveryMethods = other.deliveryMethods; + this.additionalConfig = other.additionalConfig; this.originatorType = other.originatorType; this.originatorEntityId = other.originatorEntityId; this.ruleId = other.ruleId; - this.additionalConfig = other.additionalConfig; this.status = other.status; + this.templateContext = other.templateContext; } @JsonIgnore @Override public String getName() { - return notificationReason; + return type; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java index 1ecff879f6..71bddf1342 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -17,7 +17,11 @@ package org.thingsboard.server.common.data.notification; import lombok.Data; +import java.util.Map; + @Data public class NotificationRequestConfig { + private int sendingDelayInSec; + } 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 9ce62f1420..ab343cce2f 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 @@ -22,11 +22,15 @@ 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 javax.validation.Valid; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @@ -36,8 +40,10 @@ public class NotificationRule extends BaseData implements Ha private TenantId tenantId; @NotBlank private String name; - @NotBlank - private String notificationTextTemplate; + @NotNull + private NotificationTemplateId templateId; + @NotEmpty + private List deliveryMethods; @NotNull private NotificationTargetId initialNotificationTargetId; @Valid diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java new file mode 100644 index 0000000000..ff91068f84 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java @@ -0,0 +1,38 @@ +/** + * 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.settings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +@JsonIgnoreProperties(ignoreUnknown = true) +//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = NotificationDeliveryMethodConfig.class) +//@JsonSubTypes({ +//// @JsonSubTypes.Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class), +//}) +@Data +public class NotificationDeliveryMethodConfig { + + private boolean enabled; + + public NotificationDeliveryMethod getMethod() { + return null; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java new file mode 100644 index 0000000000..51ec3af828 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java @@ -0,0 +1,29 @@ +/** + * 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.settings; + +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import java.util.Map; + +@Data +public class NotificationSettings { + + // location on the screen, shown notifications count, timings of displaying + private Map deliveryMethodsConfigs; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java new file mode 100644 index 0000000000..3cbf8ca97c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.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.common.data.notification.template; + +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.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationTemplate extends BaseData implements HasTenantId, HasName { + + private TenantId tenantId; + private String name; + private NotificationTemplateConfig configuration; + // add notification type (notification reason) + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java new file mode 100644 index 0000000000..bc92add8f4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java @@ -0,0 +1,29 @@ +/** + * 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.template; + +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import java.util.Map; + +@Data +public class NotificationTemplateConfig { + + private NotificationTextTemplate defaultTextTemplate; + private Map textTemplates; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java new file mode 100644 index 0000000000..227e4731b2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java @@ -0,0 +1,30 @@ +/** + * 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.template; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class NotificationText { + + private String body; + private String subject; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java new file mode 100644 index 0000000000..3e0ecff9d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java @@ -0,0 +1,26 @@ +/** + * 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.template; + +import lombok.Data; + +@Data +public class NotificationTextTemplate { + + private String body; + private String subject; + +} 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 65917e9b2d..941e9459ba 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 @@ -656,18 +656,17 @@ public class ModelConstants { public static final String NOTIFICATION_TABLE_NAME = "notification"; public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; - public static final String NOTIFICATION_REASON_PROPERTY = "reason"; + public static final String NOTIFICATION_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_TEXT_PROPERTY = "text"; - public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; public static final String NOTIFICATION_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; - public static final String NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY = "text_template"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; - public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; + public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id"; + public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods"; + public static final String NOTIFICATION_REQUEST_TYPE_PROPERTY = "type"; + public static final String NOTIFICATION_REQUEST_INFO_PROPERTY = "info"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_TYPE_PROPERTY = "originator_entity_type"; @@ -676,10 +675,14 @@ public class ModelConstants { public static final String NOTIFICATION_REQUEST_RULE_ID_PROPERTY = "rule_id"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; - public static final String NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY = "notification_text_template"; + 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_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY = "initial_notification_target_id"; public static final String NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY = "escalation_config"; + public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template"; + public static final String NOTIFICATION_TEMPLATE_CONFIGURATION = "configuration"; + protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN), max(TS_COLUMN)}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index 2e6255eecd..7855e773a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; -import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -54,8 +53,8 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY, nullable = false) private UUID recipientId; - @Column(name = ModelConstants.NOTIFICATION_REASON_PROPERTY, nullable = false) - private String reason; + @Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false) + private String type; @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) private String text; @@ -64,10 +63,6 @@ public class NotificationEntity extends BaseSqlEntity { @Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) - private NotificationSeverity severity; - @Enumerated(EnumType.STRING) @Column(name = ModelConstants.NOTIFICATION_ORIGINATOR_TYPE_PROPERTY) private NotificationOriginatorType originatorType; @@ -83,12 +78,9 @@ public class NotificationEntity extends BaseSqlEntity { setCreatedTime(notification.getCreatedTime()); setRequestId(getUuid(notification.getRequestId())); setRecipientId(getUuid(notification.getRecipientId())); - setReason(notification.getReason()); + setType(notification.getType()); setText(notification.getText()); - if (notification.getInfo() != null) { - setInfo(JacksonUtil.valueToTree(notification.getInfo())); - } - setSeverity(notification.getSeverity()); + setInfo(toJson(notification.getInfo())); setOriginatorType(notification.getOriginatorType()); setStatus(notification.getStatus()); } @@ -100,12 +92,9 @@ public class NotificationEntity extends BaseSqlEntity { notification.setCreatedTime(createdTime); notification.setRequestId(createId(requestId, NotificationRequestId::new)); notification.setRecipientId(createId(recipientId, UserId::new)); - notification.setReason(reason); + notification.setText(type); notification.setText(text); - if (info != null) { - notification.setInfo(JacksonUtil.treeToValue(info, NotificationInfo.class)); - } - notification.setSeverity(severity); + notification.setInfo(fromJson(info, NotificationInfo.class)); notification.setOriginatorType(originatorType); notification.setStatus(status); return notification; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 52b5aee2b9..1001f96be0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -18,23 +18,22 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.EntityIdFactory; 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.NotificationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationOriginatorType; 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.NotificationSeverity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -44,7 +43,9 @@ import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Table; +import java.util.Arrays; import java.util.UUID; +import java.util.stream.Collectors; @Data @EqualsAndHashCode(callSuper = true) @@ -59,19 +60,22 @@ public class NotificationRequestEntity extends BaseSqlEntity { @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) private String name; - @Column(name = ModelConstants.NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY, nullable = false) - private String notificationTextTemplate; + @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; @Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY) private UUID initialNotificationTargetId; @@ -63,7 +71,8 @@ public class NotificationRuleEntity extends BaseSqlEntity { setCreatedTime(notificationRule.getCreatedTime()); setTenantId(getUuid(notificationRule.getTenantId())); setName(notificationRule.getName()); - setNotificationTextTemplate(notificationRule.getNotificationTextTemplate()); + setTemplateId(getUuid(notificationRule.getTemplateId())); + setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ',')); setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); setEscalationConfig(toJson(notificationRule.getEscalationConfig())); } @@ -75,7 +84,11 @@ public class NotificationRuleEntity extends BaseSqlEntity { notificationRule.setCreatedTime(createdTime); notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationRule.setName(name); - notificationRule.setNotificationTextTemplate(notificationTextTemplate); + notificationRule.setTemplateId(createId(templateId, NotificationTemplateId::new)); + if (deliveryMethods != null) { + notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ',')) + .filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList())); + } notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new)); notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class)); return notificationRule; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index 37a4c51fda..6f274acc70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -20,14 +20,12 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; @@ -59,7 +57,7 @@ public class NotificationTargetEntity extends BaseSqlEntity setCreatedTime(notificationTarget.getCreatedTime()); setTenantId(getUuid(notificationTarget.getTenantId())); setName(notificationTarget.getName()); - setConfiguration(JacksonUtil.valueToTree(notificationTarget.getConfiguration())); + setConfiguration(toJson(notificationTarget.getConfiguration())); } @Override @@ -69,9 +67,7 @@ public class NotificationTargetEntity extends BaseSqlEntity notificationTarget.setCreatedTime(createdTime); notificationTarget.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationTarget.setName(name); - if (configuration != null) { - notificationTarget.setConfiguration(JacksonUtil.treeToValue(configuration, NotificationTargetConfig.class)); - } + notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class)); return notificationTarget; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java new file mode 100644 index 0000000000..ba6f46accd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java @@ -0,0 +1,74 @@ +/** + * 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.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +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.Table; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.NOTIFICATION_TEMPLATE_TABLE_NAME) +public class NotificationTemplateEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY, nullable = false) + private UUID tenantId; + + @Column(name = ModelConstants.NAME_PROPERTY, nullable = false) + private String name; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION, nullable = false) + private JsonNode configuration; + + public NotificationTemplateEntity() {} + + public NotificationTemplateEntity(NotificationTemplate notificationTemplate) { + setId(notificationTemplate.getUuidId()); + setCreatedTime(notificationTemplate.getCreatedTime()); + setTenantId(getUuid(notificationTemplate.getTenantId())); + setName(notificationTemplate.getName()); + setConfiguration(toJson(notificationTemplate.getConfiguration())); + } + + @Override + public NotificationTemplate toData() { + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setId(new NotificationTemplateId(id)); + notificationTemplate.setCreatedTime(createdTime); + notificationTemplate.setTenantId(createId(tenantId, TenantId::fromUUID)); + notificationTemplate.setName(name); + notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class)); + return notificationTemplate; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java index 8e6c66fcf8..2d96742ee3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java @@ -69,6 +69,9 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< @Column(name = ModelConstants.USER_LAST_NAME_PROPERTY) private String lastName; + @Column(name = ModelConstants.PHONE_PROPERTY) + private String phone; + @Type(type = "json") @Column(name = ModelConstants.USER_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; @@ -91,6 +94,7 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< this.email = user.getEmail(); this.firstName = user.getFirstName(); this.lastName = user.getLastName(); + this.phone = user.getPhone(); this.additionalInfo = user.getAdditionalInfo(); } @@ -118,6 +122,7 @@ public class UserEntity extends BaseSqlEntity implements SearchTextEntity< user.setEmail(email); user.setFirstName(firstName); user.setLastName(lastName); + user.setPhone(phone); user.setAdditionalInfo(additionalInfo); return user; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java new file mode 100644 index 0000000000..6e14ee1fac --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -0,0 +1,40 @@ +/** + * 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; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.NotificationTemplateId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; + +@Service +@RequiredArgsConstructor +public class DefaultNotificationTemplateService implements NotificationTemplateService { + + private final NotificationTemplateDao notificationTemplateDao; + + @Override + public NotificationTemplate findNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) { + return notificationTemplateDao.findById(tenantId, id.getId()); + } + + @Override + public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) { + return notificationTemplateDao.save(tenantId, notificationTemplate); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java new file mode 100644 index 0000000000..7417c7f43d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java @@ -0,0 +1,22 @@ +/** + * 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; + +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.Dao; + +public interface NotificationTemplateDao extends Dao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java new file mode 100644 index 0000000000..b549ee80e3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java @@ -0,0 +1,46 @@ +/** + * 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.sql.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.notification.template.NotificationTemplate; +import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; +import org.thingsboard.server.dao.notification.NotificationTemplateDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.UUID; + +@Component +@SqlDao +@RequiredArgsConstructor +public class JpaNotificationTemplateDao extends JpaAbstractDao implements NotificationTemplateDao { + + private final NotificationTemplateRepository notificationTemplateRepository; + + @Override + protected Class getEntityClass() { + return NotificationTemplateEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return notificationTemplateRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 40ae696979..b6d22a62fc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -32,8 +32,7 @@ import java.util.UUID; public interface NotificationRequestRepository extends JpaRepository { @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + - "(lower(r.notificationReason) LIKE lower(concat('%', :searchText, '%')) OR " + - "lower(r.textTemplate) LIKE lower(concat('%', :searchText, '%')))") + "(lower(r.type) LIKE lower(concat('%', :searchText, '%')))") Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java new file mode 100644 index 0000000000..5d007fc677 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java @@ -0,0 +1,26 @@ +/** + * 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.sql.notification; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.model.sql.NotificationTemplateEntity; + +import java.util.UUID; + +@Repository +public interface NotificationTemplateRepository extends JpaRepository { +} diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 3332034f8e..d09fc97abd 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,6 +431,7 @@ CREATE TABLE IF NOT EXISTS tb_user ( email varchar(255) UNIQUE, first_name varchar(255), last_name varchar(255), + phone varchar(255), search_text varchar(255), tenant_id uuid ); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java index dc57a876fd..88d9cc76be 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java @@ -25,10 +25,11 @@ public interface NotificationManager { NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); - void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); + + void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); + } 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 f87debd4dc..64921ef299 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 @@ -53,25 +53,25 @@ public class TbNotificationNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - NotificationRequest notificationRequest = NotificationRequest.builder() - .tenantId(ctx.getTenantId()) - .targetId(new NotificationTargetId(config.getTargetId())) - .notificationReason(config.getNotificationReason()) - .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) - .notificationSeverity(config.getNotificationSeverity()) - .originatorType(NotificationOriginatorType.RULE_NODE) - .originatorEntityId(ctx.getTenantId()) - .build(); - withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { - return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); - }), - r -> { - TbMsgMetaData msgMetaData = msg.getMetaData().copy(); - msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); - msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); - ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); - }, - e -> ctx.tellFailure(msg, e)); +// NotificationRequest notificationRequest = NotificationRequest.builder() +// .tenantId(ctx.getTenantId()) +// .targetId(new NotificationTargetId(config.getTargetId())) +// .type(config.getNotificationReason()) +// .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) +// .notificationSeverity(config.getNotificationSeverity()) +// .originatorType(NotificationOriginatorType.RULE_NODE) +// .originatorEntityId(ctx.getTenantId()) +// .build(); +// withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { +// return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); +// }), +// r -> { +// TbMsgMetaData msgMetaData = msg.getMetaData().copy(); +// msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); +// msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); +// ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); +// }, +// e -> ctx.tellFailure(msg, e)); } private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException {