Browse Source

Multiple notification delivery methods; notification templates

pull/7511/head
ViacheslavKlimov 4 years ago
parent
commit
deb2867df1
  1. 37
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 40
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  3. 129
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java
  4. 40
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java
  6. 56
      application/src/main/java/org/thingsboard/server/service/notification/NotificationTemplateUtil.java
  7. 49
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  8. 31
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  9. 53
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  10. 8
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  11. 42
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  12. 24
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  13. 18
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  15. 13
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  16. 2
      application/src/test/java/org/thingsboard/server/service/notification/NotificationsClient.java
  17. 31
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateService.java
  18. 11
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  19. 30
      common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java
  20. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java
  21. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java
  22. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationOriginatorType.java
  23. 38
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java
  24. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java
  25. 10
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  26. 38
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java
  27. 29
      common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java
  28. 35
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  29. 29
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java
  30. 30
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationText.java
  31. 26
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTextTemplate.java
  32. 17
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  33. 23
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java
  34. 57
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java
  35. 21
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  36. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java
  37. 74
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java
  38. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java
  39. 40
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  40. 22
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTemplateDao.java
  41. 46
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTemplateDao.java
  42. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java
  43. 26
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTemplateRepository.java
  44. 1
      dao/src/main/resources/sql/schema-entities.sql
  45. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationManager.java
  46. 38
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java

37
application/src/main/data/upgrade/3.4.2/schema_update.sql → 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 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 ( CREATE TABLE IF NOT EXISTS notification_rule (
id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY, id UUID NOT NULL CONSTRAINT notification_rule_pkey PRIMARY KEY,
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL, tenant_id UUID NOT NULL,
name VARCHAR(255) 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), initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id),
escalation_config VARCHAR(500) escalation_config VARCHAR(500)
); );
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID;
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (
id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY, id UUID NOT NULL CONSTRAINT notification_request_pkey PRIMARY KEY,
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL, tenant_id UUID NOT NULL,
target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id),
notification_reason VARCHAR NOT NULL, type VARCHAR(255) NOT NULL,
text_template VARCHAR NOT NULL, template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id),
notification_info VARCHAR(1000), info VARCHAR(1000),
notification_severity VARCHAR(32), delivery_methods VARCHAR(255),
additional_config VARCHAR(1000),
originator_type VARCHAR(32) NOT NULL, originator_type VARCHAR(32) NOT NULL,
originator_entity_id UUID, originator_entity_id UUID,
originator_entity_type VARCHAR(32), originator_entity_type VARCHAR(32),
rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id), rule_id UUID NULL CONSTRAINT fk_notification_request_rule_id REFERENCES notification_rule(id),
additional_config VARCHAR(1000),
status VARCHAR(32) 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); 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 ( CREATE TABLE IF NOT EXISTS notification (
id UUID NOT NULL, id UUID NOT NULL,
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
request_id UUID NOT NULL CONSTRAINT fk_notification_request_id request_id UUID NOT NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE,
REFERENCES notification_request(id) ON DELETE CASCADE, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE,
recipient_id UUID NOT NULL, type VARCHAR(255) NOT NULL,
reason VARCHAR NOT NULL, text VARCHAR(1000) NOT NULL,
text VARCHAR NOT NULL,
info VARCHAR(1000), info VARCHAR(1000),
severity VARCHAR(32),
originator_type VARCHAR(32) NOT NULL, originator_type VARCHAR(32) NOT NULL,
status VARCHAR(32) status VARCHAR(32)
) PARTITION BY RANGE (created_time); ) PARTITION BY RANGE (created_time);
CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id); 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_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); 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);

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

@ -91,33 +91,23 @@ public class NotificationController extends BaseController {
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { @AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// todo: check permission for notification target // todo: check permission for notification target
if (notificationRequest.getId() == null) { if (notificationRequest.getId() != null) {
accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest); throw new IllegalArgumentException("Notification request cannot be updated. You may only cancel/delete it");
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);
existingNotificationRequest.setNotificationReason(notificationRequest.getNotificationReason()); accessControlService.checkPermission(user, Resource.NOTIFICATION_REQUEST, Operation.CREATE, null, notificationRequest);
existingNotificationRequest.setTextTemplate(notificationRequest.getTextTemplate()); notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN);
existingNotificationRequest.setNotificationSeverity(notificationRequest.getNotificationSeverity()); notificationRequest.setOriginatorEntityId(user.getId());
return notificationManager.updateNotificationRequest(user.getTenantId(), existingNotificationRequest); 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 { // try {
// NotificationRequest savedNotificationRequest = ; // NotificationRequest savedNotificationRequest = ;
// logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED); // logEntityAction(user, EntityType.NOTIFICATION_REQUEST, savedNotificationRequest, ActionType.ADDED);

129
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java

@ -16,21 +16,26 @@
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.util.concurrent.Futures;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationManager; 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.User;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -41,45 +46,37 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService; 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.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; 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.subscription.TbSubscriptionUtils;
import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager { @RequiredArgsConstructor
public class DefaultNotificationManager extends AbstractSubscriptionService implements NotificationManager, NotificationChannel {
private final NotificationTargetService notificationTargetService; private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
private final NotificationService notificationService; private final NotificationService notificationService;
private final NotificationTemplateUtil notificationTemplateUtil;
private final DbCallbackExecutorService dbCallbackExecutorService; private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
private Map<NotificationDeliveryMethod, NotificationChannel> 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 @Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
@ -98,17 +95,18 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods());
DaoUtil.processBatches(pageLink -> { DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
}, 100, recipients -> { }, 100, recipients -> {
dbCallbackExecutorService.submit(() -> { dbCallbackExecutorService.submit(() -> {
log.debug("Sending notifications for request {} to recipients batch", savedNotificationRequest.getId()); for (NotificationDeliveryMethod deliveryMethod : savedNotificationRequest.getDeliveryMethods()) {
for (User recipient : recipients) { log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId());
try { NotificationChannel notificationChannel = channels.get(deliveryMethod);
Notification notification = createNotification(recipient, savedNotificationRequest); for (User recipient : recipients) {
onNotificationUpdate(recipient.getTenantId(), recipient.getId(), notification, true); Map<String, String> templateContext = createTemplateContext(notificationRequest, recipient);
} catch (Exception e) { NotificationText text = notificationTemplateUtil.processTemplate(textTemplates.get(deliveryMethod), templateContext);
log.error("Failed to create notification for recipient {}", recipient.getId(), e); notificationChannel.sendNotification(recipient, savedNotificationRequest, text);
} }
} }
}); });
@ -117,6 +115,17 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
return savedNotificationRequest; return savedNotificationRequest;
} }
private Map<String, String> createTemplateContext(NotificationRequest notificationRequest, User recipient) {
Map<String, String> 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) { private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) {
TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
@ -131,6 +140,27 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null);
} }
@Override
public Future<Void> 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 @Override
public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId);
@ -158,52 +188,28 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequest.getId()) .notificationRequestId(notificationRequest.getId())
.notificationInfo(notificationRequest.getNotificationInfo()) .notificationInfo(notificationRequest.getInfo())
.deleted(false) .deleted(false)
.build()); .build());
return notificationRequest; return notificationRequest;
} }
private Notification createNotification(User recipient, NotificationRequest notificationRequest) { private Future<Void> onNotificationUpdate(TenantId tenantId, UserId recipientId, Notification notification, boolean isNew) {
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<String, String> 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) {
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.notification(notification) .notification(notification)
.isNew(isNew) .isNew(isNew)
.build(); .build();
log.trace("Submitting notification update for recipient {}: {}", recipientId, update); log.trace("Submitting notification update for recipient {}: {}", recipientId, update);
wsCallBackExecutor.submit(() -> { return wsCallBackExecutor.submit(() -> {
forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> { forwardToSubscriptionManagerService(tenantId, recipientId, subscriptionManagerService -> {
subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY); subscriptionManagerService.onNotificationUpdate(tenantId, recipientId, update, TbCallback.EMPTY);
}, () -> { }, () -> TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update));
return TbSubscriptionUtils.notificationUpdateToProto(tenantId, recipientId, update); return null;
});
}); });
} }
private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) { private void onNotificationRequestUpdate(TenantId tenantId, NotificationRequestUpdate update) {
// todo: check delivery method
log.trace("Submitting notification request update: {}", update); log.trace("Submitting notification request update: {}", update);
wsCallBackExecutor.submit(() -> { wsCallBackExecutor.submit(() -> {
TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update); TransportProtos.ToCoreNotificationMsg notificationRequestUpdateProto = TbSubscriptionUtils.notificationRequestUpdateToProto(tenantId, update);
@ -215,9 +221,20 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
}); });
} }
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.WEBSOCKET;
}
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
return "notification"; return "notification";
} }
@Autowired
public void setChannels(List<NotificationChannel> channels, NotificationManager websocketNotificationChannel) {
this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c));
this.channels.put(NotificationDeliveryMethod.WEBSOCKET, (NotificationChannel) websocketNotificationChannel);
}
} }

40
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 com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.NotificationManager; 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.alarm.Alarm;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
@ -50,7 +51,8 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
private final NotificationRuleService notificationRuleService; private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
private final NotificationManager notificationManager; @Autowired @Lazy
private NotificationManager notificationManager;
private final DbCallbackExecutorService dbCallbackExecutorService; private final DbCallbackExecutorService dbCallbackExecutorService;
@Override @Override
@ -108,9 +110,9 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} else { } else {
NotificationInfo newNotificationInfo = constructNotificationInfo(alarm); NotificationInfo newNotificationInfo = constructNotificationInfo(alarm);
for (NotificationRequest notificationRequest : notificationRequests) { for (NotificationRequest notificationRequest : notificationRequests) {
NotificationInfo previousNotificationInfo = notificationRequest.getNotificationInfo(); NotificationInfo previousNotificationInfo = notificationRequest.getInfo();
if (!previousNotificationInfo.equals(newNotificationInfo)) { if (!previousNotificationInfo.equals(newNotificationInfo)) {
notificationRequest.setNotificationInfo(newNotificationInfo); notificationRequest.setInfo(newNotificationInfo);
notificationManager.updateNotificationRequest(tenantId, notificationRequest); notificationManager.updateNotificationRequest(tenantId, notificationRequest);
} }
} }
@ -127,34 +129,28 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
} }
NotificationInfo notificationInfo = constructNotificationInfo(alarm); NotificationInfo notificationInfo = constructNotificationInfo(alarm);
Map<String, String> templateContext = Map.of(
"alarmType", alarm.getType(),
"alarmId", alarm.getId().toString(),
"alarmOriginatorEntityType", alarm.getOriginator().getEntityType().toString(),
"alarmOriginatorId", alarm.getOriginator().getId().toString()
);
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.targetId(targetId) .targetId(targetId)
.notificationReason("Alarm") .type("Alarm")
.textTemplate(formatNotificationTextTemplate(notificationRule.getNotificationTextTemplate(), alarm)) .templateId(notificationRule.getTemplateId())
.notificationInfo(notificationInfo) .deliveryMethods(notificationRule.getDeliveryMethods())
.notificationSeverity(NotificationSeverity.NORMAL) // todo: from alarm severity .additionalConfig(config)
.info(notificationInfo)
.originatorType(NotificationOriginatorType.ALARM) .originatorType(NotificationOriginatorType.ALARM)
.originatorEntityId(alarm.getId()) .originatorEntityId(alarm.getId())
.ruleId(notificationRule.getId()) .ruleId(notificationRule.getId())
.additionalConfig(config) .templateContext(templateContext)
.build(); .build();
notificationManager.processNotificationRequest(tenantId, notificationRequest); notificationManager.processNotificationRequest(tenantId, notificationRequest);
} }
private String formatNotificationTextTemplate(String textTemplate, Alarm alarm) {
Map<String, String> 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) { private NotificationInfo constructNotificationInfo(Alarm alarm) {
return AlarmOriginatedNotificationInfo.builder() return AlarmOriginatedNotificationInfo.builder()
.alarmId(alarm.getId()) .alarmId(alarm.getId())

3
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java

@ -89,9 +89,8 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS
.orElse(0); .orElse(0);
if (delayInSec <= 0) return; if (delayInSec <= 0) return;
long delayInMs = TimeUnit.SECONDS.toMillis(delayInSec) - (System.currentTimeMillis() - requestTs); 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; delayInMs = 0;
// or maybe no need to process outdated notification requests ?
} }
ListenableScheduledFuture<?> scheduledTask = scheduledExecutor.schedule(() -> { ListenableScheduledFuture<?> scheduledTask = scheduledExecutor.schedule(() -> {

56
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<NotificationDeliveryMethod, NotificationTextTemplate> getTemplates(TenantId tenantId, NotificationTemplateId templateId, List<NotificationDeliveryMethod> 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<String, String> templateContext) {
return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject());
}
}

49
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<Void> 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;
}
}

31
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<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text);
NotificationDeliveryMethod getDeliveryMethod();
}

53
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<Void> 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;
}
}

8
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -28,6 +28,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
@ -149,7 +150,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final EntityQueryRepository entityQueryRepository; private final EntityQueryRepository entityQueryRepository;
private final DbTypeInfoComponent dbTypeInfoComponent; private final DbTypeInfoComponent dbTypeInfoComponent;
@Autowired @Lazy
private TelemetrySubscriptionService tsSubService; private TelemetrySubscriptionService tsSubService;
@Value("${state.defaultInactivityTimeoutInSec}") @Value("${state.defaultInactivityTimeoutInSec}")
@ -196,11 +197,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
this.dbTypeInfoComponent = dbTypeInfoComponent; this.dbTypeInfoComponent = dbTypeInfoComponent;
} }
@Autowired
public void setTsSubService(TelemetrySubscriptionService tsSubService) {
this.tsSubService = tsSubService;
}
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init(); super.init();

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

@ -15,8 +15,10 @@
*/ */
package org.thingsboard.server.service.subscription; package org.thingsboard.server.service.subscription;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
@ -86,37 +88,19 @@ import java.util.function.Predicate;
@Slf4j @Slf4j
@TbCoreComponent @TbCoreComponent
@Service @Service
@RequiredArgsConstructor
public class DefaultSubscriptionManagerService extends TbApplicationEventListener<PartitionChangeEvent> implements SubscriptionManagerService { public class DefaultSubscriptionManagerService extends TbApplicationEventListener<PartitionChangeEvent> implements SubscriptionManagerService {
@Autowired private final AttributesService attrService;
private AttributesService attrService; private final TimeseriesService tsService;
private final NotificationsTopicService notificationsTopicService;
@Autowired private final PartitionService partitionService;
private TimeseriesService tsService; private final TbServiceInfoProvider serviceInfoProvider;
private final TbQueueProducerProvider producerProvider;
@Autowired private final TbLocalSubscriptionService localSubscriptionService;
private NotificationsTopicService notificationsTopicService; private final DeviceStateService deviceStateService;
private final TbClusterService clusterService;
@Autowired private final NotificationRuleProcessingService notificationRuleProcessingService;
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 Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>(); private final Map<EntityId, Set<TbSubscription>> subscriptionsByEntityId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>(); private final Map<String, Map<Integer, TbSubscription>> subscriptionsByWsSessionId = new ConcurrentHashMap<>();

24
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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.ThingsBoardThreadFactory; 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.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos; 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.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; 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 org.thingsboard.server.service.subscription.SubscriptionManagerService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -51,23 +52,15 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
protected final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet(); protected final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet();
protected final TbClusterService clusterService; @Autowired
protected final PartitionService partitionService; protected TbClusterService clusterService;
@Autowired
protected PartitionService partitionService;
@Autowired
protected Optional<SubscriptionManagerService> subscriptionManagerService; protected Optional<SubscriptionManagerService> subscriptionManagerService;
protected ExecutorService wsCallBackExecutor; protected ExecutorService wsCallBackExecutor;
public AbstractSubscriptionService(TbClusterService clusterService,
PartitionService partitionService) {
this.clusterService = clusterService;
this.partitionService = partitionService;
}
@Autowired(required = false)
public void setSubscriptionManagerService(Optional<SubscriptionManagerService> subscriptionManagerService) {
this.subscriptionManagerService = subscriptionManagerService;
}
protected abstract String getExecutorPrefix(); protected abstract String getExecutorPrefix();
@PostConstruct @PostConstruct
@ -118,4 +111,5 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
} }
}, wsCallBackExecutor); }, wsCallBackExecutor);
} }
} }

18
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.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -55,28 +56,13 @@ import java.util.Optional;
*/ */
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService; private final AlarmService alarmService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; 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> subscriptionManagerService) {
this.subscriptionManagerService = subscriptionManagerService;
}
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
return "alarm"; return "alarm";

3
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -83,11 +83,8 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
public DefaultTelemetrySubscriptionService(AttributesService attrService, public DefaultTelemetrySubscriptionService(AttributesService attrService,
TimeseriesService tsService, TimeseriesService tsService,
@Lazy TbEntityViewService tbEntityViewService, @Lazy TbEntityViewService tbEntityViewService,
TbClusterService clusterService,
PartitionService partitionService,
TbApiUsageReportClient apiUsageClient, TbApiUsageReportClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) { TbApiUsageStateService apiUsageStateService) {
super(clusterService, partitionService);
this.attrService = attrService; this.attrService = attrService;
this.tsService = tsService; this.tsService = tsService;
this.tbEntityViewService = tbEntityViewService; this.tbEntityViewService = tbEntityViewService;

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

@ -232,12 +232,12 @@ public class NotificationApiTest extends AbstractControllerTest {
getWsClient().waitForUpdate(true); getWsClient().waitForUpdate(true);
Notification initialNotification = getWsClient().getLastDataUpdate().getUpdate(); Notification initialNotification = getWsClient().getLastDataUpdate().getUpdate();
assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification); assertThat(getMyNotifications(false, 10)).singleElement().isEqualTo(initialNotification);
assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getNotificationInfo()); assertThat(initialNotification.getInfo()).isNotNull().isEqualTo(notificationRequest.getInfo());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
NotificationInfo newNotificationInfo = new NotificationInfo(); NotificationInfo newNotificationInfo = new NotificationInfo();
newNotificationInfo.setDescription("New description"); newNotificationInfo.setDescription("New description");
notificationRequest.setNotificationInfo(newNotificationInfo); notificationRequest.setInfo(newNotificationInfo);
notificationManager.updateNotificationRequest(tenantId, notificationRequest); notificationManager.updateNotificationRequest(tenantId, notificationRequest);
getWsClient().waitForUpdate(true); getWsClient().waitForUpdate(true);
Notification updatedNotification = getWsClient().getLastDataUpdate().getUpdate(); Notification updatedNotification = getWsClient().getLastDataUpdate().getUpdate();
@ -264,7 +264,7 @@ public class NotificationApiTest extends AbstractControllerTest {
notificationTarget = saveNotificationTarget(notificationTarget); notificationTarget = saveNotificationTarget(notificationTarget);
wsSessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); 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) await().atMost(5, TimeUnit.SECONDS)
.pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> wsSessions.values().stream() .until(() -> wsSessions.values().stream()
@ -339,9 +339,10 @@ public class NotificationApiTest extends AbstractControllerTest {
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.targetId(targetId) .targetId(targetId)
.notificationReason("Test") .type("Test")
.textTemplate(text) .templateId(notificationTemplate.getId())
.notificationInfo(notificationInfo) .info(notificationInfo)
.deliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET))
.additionalConfig(config) .additionalConfig(config)
.build(); .build();
return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);

2
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"); SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
String time = format.format(new Date(notification.getCreatedTime())); 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)); System.out.println(StringUtils.repeat(System.lineSeparator(), 5));
} }

31
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);
}

11
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -45,6 +45,8 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
@NoXss @NoXss
@Length(fieldName = "last name") @Length(fieldName = "last name")
private String lastName; private String lastName;
@NoXss
private String phone;
public User() { public User() {
super(); super();
@ -62,6 +64,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.authority = user.getAuthority(); this.authority = user.getAuthority();
this.firstName = user.getFirstName(); this.firstName = user.getFirstName();
this.lastName = user.getLastName(); this.lastName = user.getLastName();
this.phone = user.getPhone();
} }
@ -141,6 +144,14 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.lastName = lastName; 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") @ApiModelProperty(position = 10, value = "Additional parameters of the user", dataType = "com.fasterxml.jackson.databind.JsonNode")
@Override @Override
public JsonNode getAdditionalInfo() { public JsonNode getAdditionalInfo() {

30
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);
}
}

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

@ -34,10 +34,11 @@ public class Notification extends BaseData<NotificationId> {
private NotificationRequestId requestId; private NotificationRequestId requestId;
private UserId recipientId; private UserId recipientId;
private String reason;
private String type; // todo: maybe to enum
private String text; private String text;
private NotificationInfo info; private NotificationInfo info;
private NotificationSeverity severity;
private NotificationOriginatorType originatorType; private NotificationOriginatorType originatorType;
private NotificationStatus status; private NotificationStatus status;

4
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSettings.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java

@ -15,6 +15,6 @@
*/ */
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
public class NotificationSettings { public enum NotificationDeliveryMethod {
// location on the screen, shown notifications count, timings of displaying WEBSOCKET, SMS, EMAIL, SLACK
} }

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

@ -16,7 +16,7 @@
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
public enum NotificationOriginatorType { public enum NotificationOriginatorType {
USER, ADMIN,
ALARM, ALARM,
RULE_NODE RULE_NODE
} }

38
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.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -47,39 +50,46 @@ public class NotificationRequest extends BaseData<NotificationRequestId> impleme
private NotificationTargetId targetId; private NotificationTargetId targetId;
@NoXss @NoXss
private String notificationReason; private String type;
@NotBlank(message = "Notification text template is missing") @NotNull
private String textTemplate; // fixme: xss private NotificationTemplateId templateId;
@Valid @Valid
private NotificationInfo notificationInfo; private NotificationInfo info;
private NotificationSeverity notificationSeverity; @NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods;
@NotNull
@Valid
private NotificationRequestConfig additionalConfig;
private NotificationOriginatorType originatorType; private NotificationOriginatorType originatorType;
private EntityId originatorEntityId; // userId, alarmId or tenantId private EntityId originatorEntityId;
private NotificationRuleId ruleId; private NotificationRuleId ruleId;
private NotificationRequestConfig additionalConfig;
private NotificationRequestStatus status; private NotificationRequestStatus status;
@JsonIgnore
private transient Map<String, String> templateContext;
public NotificationRequest(NotificationRequest other) { public NotificationRequest(NotificationRequest other) {
super(other); super(other);
this.tenantId = other.tenantId; this.tenantId = other.tenantId;
this.targetId = other.targetId; this.targetId = other.targetId;
this.notificationReason = other.notificationReason; this.type = other.type;
this.textTemplate = other.textTemplate; this.templateId = other.templateId;
this.notificationInfo = other.notificationInfo; this.info = other.info;
this.notificationSeverity = other.notificationSeverity; this.deliveryMethods = other.deliveryMethods;
this.additionalConfig = other.additionalConfig;
this.originatorType = other.originatorType; this.originatorType = other.originatorType;
this.originatorEntityId = other.originatorEntityId; this.originatorEntityId = other.originatorEntityId;
this.ruleId = other.ruleId; this.ruleId = other.ruleId;
this.additionalConfig = other.additionalConfig;
this.status = other.status; this.status = other.status;
this.templateContext = other.templateContext;
} }
@JsonIgnore @JsonIgnore
@Override @Override
public String getName() { public String getName() {
return notificationReason; return type;
} }
} }

4
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 lombok.Data;
import java.util.Map;
@Data @Data
public class NotificationRequestConfig { public class NotificationRequestConfig {
private int sendingDelayInSec; private int sendingDelayInSec;
} }

10
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.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -36,8 +40,10 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private TenantId tenantId; private TenantId tenantId;
@NotBlank @NotBlank
private String name; private String name;
@NotBlank @NotNull
private String notificationTextTemplate; private NotificationTemplateId templateId;
@NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods;
@NotNull @NotNull
private NotificationTargetId initialNotificationTargetId; private NotificationTargetId initialNotificationTargetId;
@Valid @Valid

38
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;
}
}

29
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<NotificationDeliveryMethod, NotificationDeliveryMethodConfig> deliveryMethodsConfigs;
}

35
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<NotificationTemplateId> implements HasTenantId, HasName {
private TenantId tenantId;
private String name;
private NotificationTemplateConfig configuration;
// add notification type (notification reason)
}

29
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<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates;
}

30
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;
}

26
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;
}

17
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_TABLE_NAME = "notification";
public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; 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_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_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_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_STATUS_PROPERTY = "status";
public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; 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_TARGET_ID_PROPERTY = "target_id";
public static final String NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY = "text_template"; public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id";
public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods";
public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; public static final String NOTIFICATION_REQUEST_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; 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_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_ID_PROPERTY = "originator_entity_id";
public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_TYPE_PROPERTY = "originator_entity_type"; 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_REQUEST_RULE_ID_PROPERTY = "rule_id";
public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule";
public static final String NOTIFICATION_RULE_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_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_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[] 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)}; 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)};

23
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.Notification;
import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; 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.common.data.notification.NotificationStatus;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -54,8 +53,8 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
@Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY, nullable = false)
private UUID recipientId; private UUID recipientId;
@Column(name = ModelConstants.NOTIFICATION_REASON_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false)
private String reason; private String type;
@Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false)
private String text; private String text;
@ -64,10 +63,6 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
@Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)") @Formula("(SELECT r.notification_info FROM notification_request r WHERE r.id = request_id)")
private JsonNode info; private JsonNode info;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY)
private NotificationSeverity severity;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_ORIGINATOR_TYPE_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_ORIGINATOR_TYPE_PROPERTY)
private NotificationOriginatorType originatorType; private NotificationOriginatorType originatorType;
@ -83,12 +78,9 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
setCreatedTime(notification.getCreatedTime()); setCreatedTime(notification.getCreatedTime());
setRequestId(getUuid(notification.getRequestId())); setRequestId(getUuid(notification.getRequestId()));
setRecipientId(getUuid(notification.getRecipientId())); setRecipientId(getUuid(notification.getRecipientId()));
setReason(notification.getReason()); setType(notification.getType());
setText(notification.getText()); setText(notification.getText());
if (notification.getInfo() != null) { setInfo(toJson(notification.getInfo()));
setInfo(JacksonUtil.valueToTree(notification.getInfo()));
}
setSeverity(notification.getSeverity());
setOriginatorType(notification.getOriginatorType()); setOriginatorType(notification.getOriginatorType());
setStatus(notification.getStatus()); setStatus(notification.getStatus());
} }
@ -100,12 +92,9 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
notification.setCreatedTime(createdTime); notification.setCreatedTime(createdTime);
notification.setRequestId(createId(requestId, NotificationRequestId::new)); notification.setRequestId(createId(requestId, NotificationRequestId::new));
notification.setRecipientId(createId(recipientId, UserId::new)); notification.setRecipientId(createId(recipientId, UserId::new));
notification.setReason(reason); notification.setText(type);
notification.setText(text); notification.setText(text);
if (info != null) { notification.setInfo(fromJson(info, NotificationInfo.class));
notification.setInfo(JacksonUtil.treeToValue(info, NotificationInfo.class));
}
notification.setSeverity(severity);
notification.setOriginatorType(originatorType); notification.setOriginatorType(originatorType);
notification.setStatus(status); notification.setStatus(status);
return notification; return notification;

57
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 com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; 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.EntityIdFactory;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.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.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; 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.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
@ -44,7 +43,9 @@ import javax.persistence.Entity;
import javax.persistence.EnumType; import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -59,19 +60,22 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
@Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY, nullable = false)
private UUID targetId; private UUID targetId;
@Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_TYPE_PROPERTY, nullable = false)
private String notificationReason; private String type;
@Column(name = ModelConstants.NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY, nullable = false)
private String textTemplate; private UUID templateId;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_REQUEST_INFO_PROPERTY)
private JsonNode notificationInfo; private JsonNode info;
@Enumerated(EnumType.STRING) @Column(name = ModelConstants.NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY, nullable = false)
@Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY) private String deliveryMethods;
private NotificationSeverity notificationSeverity;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY)
private JsonNode additionalConfig;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY, nullable = false)
@ -87,10 +91,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
@Column(name = ModelConstants.NOTIFICATION_REQUEST_RULE_ID_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_REQUEST_RULE_ID_PROPERTY)
private UUID ruleId; private UUID ruleId;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY)
private JsonNode additionalConfig;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_REQUEST_STATUS_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_REQUEST_STATUS_PROPERTY)
private NotificationRequestStatus status; private NotificationRequestStatus status;
@ -102,17 +102,17 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
setCreatedTime(notificationRequest.getCreatedTime()); setCreatedTime(notificationRequest.getCreatedTime());
setTenantId(getUuid(notificationRequest.getTenantId())); setTenantId(getUuid(notificationRequest.getTenantId()));
setTargetId(getUuid(notificationRequest.getTargetId())); setTargetId(getUuid(notificationRequest.getTargetId()));
setNotificationReason(notificationRequest.getNotificationReason()); setType(notificationRequest.getType());
setTextTemplate(notificationRequest.getTextTemplate()); setTemplateId(getUuid(notificationRequest.getTemplateId()));
setNotificationInfo(toJson(notificationRequest.getNotificationInfo())); setInfo(toJson(notificationRequest.getInfo()));
setNotificationSeverity(notificationRequest.getNotificationSeverity()); setDeliveryMethods(StringUtils.join(notificationRequest.getDeliveryMethods(), ','));
setAdditionalConfig(toJson(notificationRequest.getAdditionalConfig()));
setOriginatorType(notificationRequest.getOriginatorType()); setOriginatorType(notificationRequest.getOriginatorType());
if (notificationRequest.getOriginatorEntityId() != null) { if (notificationRequest.getOriginatorEntityId() != null) {
setOriginatorEntityId(notificationRequest.getOriginatorEntityId().getId()); setOriginatorEntityId(notificationRequest.getOriginatorEntityId().getId());
setOriginatorEntityType(notificationRequest.getOriginatorEntityId().getEntityType()); setOriginatorEntityType(notificationRequest.getOriginatorEntityId().getEntityType());
} }
setRuleId(getUuid(notificationRequest.getRuleId())); setRuleId(getUuid(notificationRequest.getRuleId()));
setAdditionalConfig(toJson(notificationRequest.getAdditionalConfig()));
setStatus(notificationRequest.getStatus()); setStatus(notificationRequest.getStatus());
} }
@ -123,16 +123,19 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
notificationRequest.setCreatedTime(createdTime); notificationRequest.setCreatedTime(createdTime);
notificationRequest.setTenantId(createId(tenantId, TenantId::new)); notificationRequest.setTenantId(createId(tenantId, TenantId::new));
notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new)); notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new));
notificationRequest.setNotificationReason(notificationReason); notificationRequest.setType(type);
notificationRequest.setTextTemplate(textTemplate); notificationRequest.setTemplateId(createId(templateId, NotificationTemplateId::new));
notificationRequest.setNotificationInfo(fromJson(notificationInfo, NotificationInfo.class)); notificationRequest.setInfo(fromJson(info, NotificationInfo.class));
notificationRequest.setNotificationSeverity(notificationSeverity); if (deliveryMethods != null) {
notificationRequest.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ','))
.filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList()));
}
notificationRequest.setAdditionalConfig(fromJson(additionalConfig, NotificationRequestConfig.class));
notificationRequest.setOriginatorType(originatorType); notificationRequest.setOriginatorType(originatorType);
if (originatorEntityId != null) { if (originatorEntityId != null) {
notificationRequest.setOriginatorEntityId(EntityIdFactory.getByTypeAndUuid(originatorEntityType, originatorEntityId)); notificationRequest.setOriginatorEntityId(EntityIdFactory.getByTypeAndUuid(originatorEntityType, originatorEntityId));
} }
notificationRequest.setRuleId(createId(ruleId, NotificationRuleId::new)); notificationRequest.setRuleId(createId(ruleId, NotificationRuleId::new));
notificationRequest.setAdditionalConfig(fromJson(additionalConfig, NotificationRequestConfig.class));
notificationRequest.setStatus(status); notificationRequest.setStatus(status);
return notificationRequest; return notificationRequest;
} }

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

@ -18,11 +18,14 @@ package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; 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.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.NotificationEscalationConfig; import org.thingsboard.server.common.data.notification.rule.NotificationEscalationConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -32,7 +35,9 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Data @EqualsAndHashCode(callSuper = true) @Data @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@ -46,8 +51,11 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NAME_PROPERTY, nullable = false) @Column(name = ModelConstants.NAME_PROPERTY, nullable = false)
private String name; private String name;
@Column(name = ModelConstants.NOTIFICATION_RULE_NOTIFICATION_TEXT_TEMPLATE_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY, nullable = false)
private String notificationTextTemplate; 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) @Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY)
private UUID initialNotificationTargetId; private UUID initialNotificationTargetId;
@ -63,7 +71,8 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setCreatedTime(notificationRule.getCreatedTime()); setCreatedTime(notificationRule.getCreatedTime());
setTenantId(getUuid(notificationRule.getTenantId())); setTenantId(getUuid(notificationRule.getTenantId()));
setName(notificationRule.getName()); setName(notificationRule.getName());
setNotificationTextTemplate(notificationRule.getNotificationTextTemplate()); setTemplateId(getUuid(notificationRule.getTemplateId()));
setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ','));
setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId()));
setEscalationConfig(toJson(notificationRule.getEscalationConfig())); setEscalationConfig(toJson(notificationRule.getEscalationConfig()));
} }
@ -75,7 +84,11 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setCreatedTime(createdTime); notificationRule.setCreatedTime(createdTime);
notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationRule.setTenantId(createId(tenantId, TenantId::fromUUID));
notificationRule.setName(name); 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.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new));
notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class)); notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class));
return notificationRule; return notificationRule;

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java

@ -20,14 +20,12 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; 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.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; 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.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.SearchTextEntity;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column; import javax.persistence.Column;
@ -59,7 +57,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
setCreatedTime(notificationTarget.getCreatedTime()); setCreatedTime(notificationTarget.getCreatedTime());
setTenantId(getUuid(notificationTarget.getTenantId())); setTenantId(getUuid(notificationTarget.getTenantId()));
setName(notificationTarget.getName()); setName(notificationTarget.getName());
setConfiguration(JacksonUtil.valueToTree(notificationTarget.getConfiguration())); setConfiguration(toJson(notificationTarget.getConfiguration()));
} }
@Override @Override
@ -69,9 +67,7 @@ public class NotificationTargetEntity extends BaseSqlEntity<NotificationTarget>
notificationTarget.setCreatedTime(createdTime); notificationTarget.setCreatedTime(createdTime);
notificationTarget.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationTarget.setTenantId(createId(tenantId, TenantId::fromUUID));
notificationTarget.setName(name); notificationTarget.setName(name);
if (configuration != null) { notificationTarget.setConfiguration(fromJson(configuration, NotificationTargetConfig.class));
notificationTarget.setConfiguration(JacksonUtil.treeToValue(configuration, NotificationTargetConfig.class));
}
return notificationTarget; return notificationTarget;
} }

74
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<NotificationTemplate> {
@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;
}
}

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/UserEntity.java

@ -69,6 +69,9 @@ public class UserEntity extends BaseSqlEntity<User> implements SearchTextEntity<
@Column(name = ModelConstants.USER_LAST_NAME_PROPERTY) @Column(name = ModelConstants.USER_LAST_NAME_PROPERTY)
private String lastName; private String lastName;
@Column(name = ModelConstants.PHONE_PROPERTY)
private String phone;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.USER_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.USER_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@ -91,6 +94,7 @@ public class UserEntity extends BaseSqlEntity<User> implements SearchTextEntity<
this.email = user.getEmail(); this.email = user.getEmail();
this.firstName = user.getFirstName(); this.firstName = user.getFirstName();
this.lastName = user.getLastName(); this.lastName = user.getLastName();
this.phone = user.getPhone();
this.additionalInfo = user.getAdditionalInfo(); this.additionalInfo = user.getAdditionalInfo();
} }
@ -118,6 +122,7 @@ public class UserEntity extends BaseSqlEntity<User> implements SearchTextEntity<
user.setEmail(email); user.setEmail(email);
user.setFirstName(firstName); user.setFirstName(firstName);
user.setLastName(lastName); user.setLastName(lastName);
user.setPhone(phone);
user.setAdditionalInfo(additionalInfo); user.setAdditionalInfo(additionalInfo);
return user; return user;
} }

40
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);
}
}

22
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<NotificationTemplate> {
}

46
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<NotificationTemplateEntity, NotificationTemplate> implements NotificationTemplateDao {
private final NotificationTemplateRepository notificationTemplateRepository;
@Override
protected Class<NotificationTemplateEntity> getEntityClass() {
return NotificationTemplateEntity.class;
}
@Override
protected JpaRepository<NotificationTemplateEntity, UUID> getRepository() {
return notificationTemplateRepository;
}
}

3
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<NotificationRequestEntity, UUID> { public interface NotificationRequestRepository extends JpaRepository<NotificationRequestEntity, UUID> {
@Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " +
"(lower(r.notificationReason) LIKE lower(concat('%', :searchText, '%')) OR " + "(lower(r.type) LIKE lower(concat('%', :searchText, '%')))")
"lower(r.textTemplate) LIKE lower(concat('%', :searchText, '%')))")
Page<NotificationRequestEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, Page<NotificationRequestEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText, Pageable pageable); @Param("searchText") String searchText, Pageable pageable);

26
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<NotificationTemplateEntity, UUID> {
}

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

@ -431,6 +431,7 @@ CREATE TABLE IF NOT EXISTS tb_user (
email varchar(255) UNIQUE, email varchar(255) UNIQUE,
first_name varchar(255), first_name varchar(255),
last_name varchar(255), last_name varchar(255),
phone varchar(255),
search_text varchar(255), search_text varchar(255),
tenant_id uuid tenant_id uuid
); );

5
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); NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId); void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest); NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
} }

38
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 @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
NotificationRequest notificationRequest = NotificationRequest.builder() // NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(ctx.getTenantId()) // .tenantId(ctx.getTenantId())
.targetId(new NotificationTargetId(config.getTargetId())) // .targetId(new NotificationTargetId(config.getTargetId()))
.notificationReason(config.getNotificationReason()) // .type(config.getNotificationReason())
.textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) // .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg))
.notificationSeverity(config.getNotificationSeverity()) // .notificationSeverity(config.getNotificationSeverity())
.originatorType(NotificationOriginatorType.RULE_NODE) // .originatorType(NotificationOriginatorType.RULE_NODE)
.originatorEntityId(ctx.getTenantId()) // .originatorEntityId(ctx.getTenantId())
.build(); // .build();
withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> { // withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> {
return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest); // return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest);
}), // }),
r -> { // r -> {
TbMsgMetaData msgMetaData = msg.getMetaData().copy(); // TbMsgMetaData msgMetaData = msg.getMetaData().copy();
msgMetaData.putValue("notificationRequestId", r.getUuidId().toString()); // msgMetaData.putValue("notificationRequestId", r.getUuidId().toString());
msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate()); // msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate());
ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData)); // ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData));
}, // },
e -> ctx.tellFailure(msg, e)); // e -> ctx.tellFailure(msg, e));
} }
private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException { private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException {

Loading…
Cancel
Save